react-native-ble-nitro 1.11.0 → 1.13.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 (32) hide show
  1. package/README.md +5 -0
  2. package/android/src/main/java/com/margelo/nitro/co/zyke/ble/BleNitroBleManager.kt +72 -26
  3. package/ios/BleNitroBleManager.swift +71 -9
  4. package/ios/BlePeripheralDelegate.swift +68 -9
  5. package/lib/commonjs/index.d.ts +1 -1
  6. package/lib/commonjs/index.d.ts.map +1 -1
  7. package/lib/commonjs/index.js +2 -1
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/manager.d.ts +17 -1
  10. package/lib/commonjs/manager.d.ts.map +1 -1
  11. package/lib/commonjs/manager.js +54 -9
  12. package/lib/commonjs/manager.js.map +1 -1
  13. package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts +3 -0
  14. package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts.map +1 -1
  15. package/lib/index.d.ts +1 -1
  16. package/lib/index.js +1 -1
  17. package/lib/manager.d.ts +17 -1
  18. package/lib/manager.js +52 -8
  19. package/lib/specs/NativeBleNitro.nitro.d.ts +3 -0
  20. package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.cpp +9 -0
  21. package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.hpp +2 -0
  22. package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/HybridNativeBleNitroSpec.kt +13 -0
  23. package/nitrogen/generated/ios/c++/HybridNativeBleNitroSpecSwift.hpp +14 -0
  24. package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec.swift +2 -0
  25. package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec_cxx.swift +28 -0
  26. package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.cpp +2 -0
  27. package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.hpp +2 -0
  28. package/package.json +1 -1
  29. package/src/__tests__/index.test.ts +127 -0
  30. package/src/index.ts +1 -0
  31. package/src/manager.ts +81 -9
  32. package/src/specs/NativeBleNitro.nitro.ts +3 -0
package/README.md CHANGED
@@ -285,6 +285,11 @@ await subscription.remove();
285
285
 
286
286
  // Or unsubscribe directly
287
287
  await ble.unsubscribeFromCharacteristic(deviceId, serviceUUID, characteristicUUID);
288
+
289
+ // Check if a characteristic is currently subscribed to notifications
290
+ const isSubscribed = ble.isSubscribedToCharacteristic(deviceId, serviceUUID, characteristicUUID);
291
+ // Returns: boolean — synchronous, no async overhead
292
+ // Returns false for disconnected or unknown devices without throwing
288
293
  ```
289
294
 
290
295
  ### Real-World Examples
@@ -75,7 +75,9 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
75
75
  var connectCallback: ((success: Boolean, deviceId: String, error: String) -> Unit)? = null,
76
76
  var disconnectCallback: ((deviceId: String, interrupted: Boolean, error: String) -> Unit)? = null,
77
77
  var serviceDiscoveryCallback: ((success: Boolean, error: String) -> Unit)? = null,
78
- var characteristicSubscriptions: MutableMap<String, (characteristicId: String, data: ArrayBuffer) -> Unit> = mutableMapOf()
78
+ // Key: "serviceId:characteristicId" for correct scoping when the same
79
+ // characteristic UUID exists under multiple services.
80
+ var characteristicSubscriptions: MutableMap<String, (characteristicId: String, data: ArrayBuffer) -> Unit> = ConcurrentHashMap()
79
81
  )
80
82
 
81
83
  // Sealed class for GATT operations that need to be queued
@@ -264,18 +266,21 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
264
266
  private fun createGattCallback(deviceId: String): BluetoothGattCallback {
265
267
  return object : BluetoothGattCallback() {
266
268
  override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
267
- val callbacks = deviceCallbacks[deviceId]
268
-
269
269
  when (newState) {
270
270
  BluetoothProfile.STATE_CONNECTED -> {
271
- callbacks?.connectCallback?.invoke(true, deviceId, "")
271
+ var connectCb: ((Boolean, String, String) -> Unit)? = null
272
+ deviceCallbacks.compute(deviceId) { _, callbacks ->
273
+ connectCb = callbacks?.connectCallback
274
+ callbacks?.copy(connectCallback = null)
275
+ }
276
+ connectCb?.invoke(true, deviceId, "")
272
277
  }
273
278
  BluetoothProfile.STATE_DISCONNECTED -> {
274
279
  // Clean up
275
280
  connectedDevices.remove(deviceId)
276
281
  val interrupted = status != BluetoothGatt.GATT_SUCCESS
277
- callbacks?.disconnectCallback?.invoke(deviceId, interrupted, if (interrupted) "Connection lost" else "")
278
- deviceCallbacks.remove(deviceId)
282
+ val cb = deviceCallbacks.remove(deviceId)
283
+ cb?.disconnectCallback?.invoke(deviceId, interrupted, if (interrupted) "Connection lost" else "")
279
284
  gatt.close()
280
285
  }
281
286
  }
@@ -352,17 +357,23 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
352
357
  override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
353
358
  // Handle characteristic notifications
354
359
  val characteristicId = characteristic.uuid.toString()
360
+ val serviceId = characteristic.service?.uuid?.toString()
361
+ if (serviceId == null) {
362
+ android.util.Log.w("BleNitro", "onCharacteristicChanged: characteristic.service is null for $characteristicId")
363
+ return
364
+ }
365
+ val subscriptionKey = "$serviceId:$characteristicId"
355
366
  val value = characteristic.value ?: byteArrayOf()
356
-
367
+
357
368
  // Create direct ByteBuffer as required by ArrayBuffer.wrap()
358
369
  val directBuffer = java.nio.ByteBuffer.allocateDirect(value.size)
359
370
  directBuffer.put(value)
360
371
  directBuffer.flip()
361
-
372
+
362
373
  val data = ArrayBuffer.wrap(directBuffer)
363
-
374
+
364
375
  val callbacks = deviceCallbacks[deviceId]
365
- callbacks?.characteristicSubscriptions?.get(characteristicId)?.invoke(characteristicId, data)
376
+ callbacks?.characteristicSubscriptions?.get(subscriptionKey)?.invoke(characteristicId, data)
366
377
  }
367
378
 
368
379
  override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
@@ -649,6 +660,12 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
649
660
  try {
650
661
  val gatt = connectedDevices[deviceId]
651
662
  if (gatt != null) {
663
+ var pendingConnect: ((Boolean, String, String) -> Unit)? = null
664
+ deviceCallbacks.compute(deviceId) { _, callbacks ->
665
+ pendingConnect = callbacks?.connectCallback
666
+ callbacks?.copy(connectCallback = null)
667
+ }
668
+ pendingConnect?.invoke(false, deviceId, "Connection cancelled")
652
669
  gatt.disconnect()
653
670
  callback(true, "")
654
671
  } else {
@@ -733,6 +750,12 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
733
750
  }
734
751
  }
735
752
 
753
+ override fun discoverServicesWithCharacteristics(deviceId: String, callback: (success: Boolean, error: String) -> Unit) {
754
+ // On Android, discoverServices() already discovers characteristics automatically.
755
+ // Delegate directly to discoverServices.
756
+ discoverServices(deviceId, callback)
757
+ }
758
+
736
759
  override fun getServices(deviceId: String): Array<String> {
737
760
  return try {
738
761
  val gatt = connectedDevices[deviceId]
@@ -896,14 +919,11 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
896
919
  return
897
920
  }
898
921
 
899
- // Store the update callback
900
- val callbacks = deviceCallbacks[deviceId]
901
- if (callbacks != null) {
902
- callbacks.characteristicSubscriptions[characteristicId] = updateCallback
903
- }
922
+ val subscriptionKey = "$serviceId:$characteristicId"
904
923
 
905
- // Write to the CCCD descriptor to enable notifications on the remote device
906
- // This must be queued to ensure sequential GATT operations
924
+ // Write to the CCCD descriptor to enable notifications on the remote device.
925
+ // The update callback is stored only after the descriptor write succeeds
926
+ // so that isSubscribedToCharacteristic reflects actual BLE state.
907
927
  val descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
908
928
  if (descriptor != null) {
909
929
  enqueueGattOperation(
@@ -912,11 +932,19 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
912
932
  descriptor = descriptor,
913
933
  value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE,
914
934
  deviceId = deviceId,
915
- callback = completionCallback
935
+ callback = { success, error ->
936
+ if (success) {
937
+ val callbacks = deviceCallbacks[deviceId]
938
+ callbacks?.characteristicSubscriptions?.set(subscriptionKey, updateCallback)
939
+ }
940
+ completionCallback(success, error)
941
+ }
916
942
  )
917
943
  )
918
944
  } else {
919
- // No CCCD descriptor - some characteristics may not need it
945
+ // No CCCD descriptor - store callback and report success
946
+ val callbacks = deviceCallbacks[deviceId]
947
+ callbacks?.characteristicSubscriptions?.set(subscriptionKey, updateCallback)
920
948
  completionCallback(true, "")
921
949
  }
922
950
 
@@ -957,12 +985,12 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
957
985
  return
958
986
  }
959
987
 
960
- // Remove the update callback
961
- val callbacks = deviceCallbacks[deviceId]
962
- callbacks?.characteristicSubscriptions?.remove(characteristicId)
988
+ val subscriptionKey = "$serviceId:$characteristicId"
963
989
 
964
- // Write to the CCCD descriptor to disable notifications on the remote device
965
- // This must be queued to ensure sequential GATT operations
990
+ // Write to the CCCD descriptor to disable notifications on the remote device.
991
+ // The subscription entry is removed only after the descriptor write succeeds,
992
+ // mirroring the subscribe pattern to avoid a stale-entry race when
993
+ // subscribe and unsubscribe are called in rapid succession.
966
994
  val descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
967
995
  if (descriptor != null) {
968
996
  enqueueGattOperation(
@@ -971,11 +999,19 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
971
999
  descriptor = descriptor,
972
1000
  value = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE,
973
1001
  deviceId = deviceId,
974
- callback = callback
1002
+ callback = { success, error ->
1003
+ if (success) {
1004
+ val callbacks = deviceCallbacks[deviceId]
1005
+ callbacks?.characteristicSubscriptions?.remove(subscriptionKey)
1006
+ }
1007
+ callback(success, error)
1008
+ }
975
1009
  )
976
1010
  )
977
1011
  } else {
978
- // No CCCD descriptor - some characteristics may not need it
1012
+ // No CCCD descriptor - remove immediately
1013
+ val callbacks = deviceCallbacks[deviceId]
1014
+ callbacks?.characteristicSubscriptions?.remove(subscriptionKey)
979
1015
  callback(true, "")
980
1016
  }
981
1017
 
@@ -984,6 +1020,16 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
984
1020
  }
985
1021
  }
986
1022
 
1023
+ override fun isSubscribedToCharacteristic(
1024
+ deviceId: String,
1025
+ serviceId: String,
1026
+ characteristicId: String
1027
+ ): Boolean {
1028
+ val callbacks = deviceCallbacks[deviceId] ?: return false
1029
+ val subscriptionKey = "$serviceId:$characteristicId"
1030
+ return callbacks.characteristicSubscriptions.containsKey(subscriptionKey)
1031
+ }
1032
+
987
1033
  // Bluetooth state management
988
1034
  override fun requestBluetoothEnable(callback: (success: Boolean, error: String) -> Unit) {
989
1035
  try {
@@ -255,17 +255,22 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
255
255
 
256
256
  public func disconnect(deviceId: String, callback: @escaping (Bool, String) -> Void) throws {
257
257
  ensureCentralManager()
258
- guard let peripheral = connectedPeripherals[deviceId] else {
259
- callback(false, "Peripheral not connected")
258
+ guard let peripheral = connectedPeripherals[deviceId] ?? findPeripheral(by: deviceId) else {
259
+ callback(false, "Peripheral not found")
260
260
  return
261
261
  }
262
-
262
+
263
+ if peripheral.state == .connecting, let delegate = peripheralDelegates[deviceId] {
264
+ delegate.connectionCallback?(false, "", "Connection cancelled")
265
+ delegate.connectionCallback = nil
266
+ }
267
+
263
268
  // Store disconnect callback in delegate
264
269
  peripheralDelegates[deviceId]?.disconnectionCallback = callback
265
-
270
+
266
271
  // Mark this as an intentional disconnection
267
272
  intentionalDisconnections.insert(deviceId)
268
-
273
+
269
274
  centralManager.cancelPeripheralConnection(peripheral)
270
275
  }
271
276
 
@@ -310,10 +315,50 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
310
315
  callback(false, "Peripheral not connected")
311
316
  return
312
317
  }
313
-
314
- peripheralDelegates[deviceId]?.serviceDiscoveryCallback = callback
318
+
319
+ guard let delegate = peripheralDelegates[deviceId] else {
320
+ callback(false, "Peripheral delegate not found")
321
+ return
322
+ }
323
+
324
+ // If services already discovered, resolve immediately to avoid
325
+ // CoreBluetooth silently skipping the didDiscoverServices callback.
326
+ if delegate.servicesDiscovered, let services = peripheral.services, !services.isEmpty {
327
+ callback(true, "")
328
+ return
329
+ }
330
+
331
+ delegate.serviceDiscoveryCallback = callback
315
332
  peripheral.discoverServices(nil)
316
333
  }
334
+
335
+ public func discoverServicesWithCharacteristics(deviceId: String, callback: @escaping (Bool, String) -> Void) throws {
336
+ guard let peripheral = connectedPeripherals[deviceId] else {
337
+ callback(false, "Peripheral not connected")
338
+ return
339
+ }
340
+
341
+ guard let delegate = peripheralDelegates[deviceId] else {
342
+ callback(false, "Peripheral delegate not found")
343
+ return
344
+ }
345
+
346
+ // If services and all characteristics are already discovered, resolve immediately.
347
+ if delegate.servicesDiscovered,
348
+ let services = peripheral.services, !services.isEmpty,
349
+ services.allSatisfy({ $0.characteristics != nil }) {
350
+ callback(true, "")
351
+ return
352
+ }
353
+
354
+ let isFirstCaller = delegate.fullDiscoveryCallbacks.isEmpty
355
+ delegate.fullDiscoveryCallbacks.append(callback)
356
+ // Only trigger discovery if this is the first caller; subsequent
357
+ // callers piggyback on the in-flight discovery via the callback array.
358
+ if isFirstCaller {
359
+ peripheral.discoverServices(nil)
360
+ }
361
+ }
317
362
 
318
363
  public func getServices(deviceId: String) throws -> [String] {
319
364
  guard let peripheral = connectedPeripherals[deviceId] else {
@@ -464,7 +509,24 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
464
509
 
465
510
  characteristic.service?.peripheral?.setNotifyValue(false, for: characteristic)
466
511
  }
467
-
512
+
513
+ // `throws` is required by the Nitro-generated HybridNativeBleNitroSpec protocol
514
+ // for all synchronous methods, even though this implementation never throws.
515
+ public func isSubscribedToCharacteristic(
516
+ deviceId: String,
517
+ serviceId: String,
518
+ characteristicId: String
519
+ ) throws -> Bool {
520
+ guard let characteristic = findCharacteristic(
521
+ deviceId: deviceId,
522
+ serviceId: serviceId,
523
+ characteristicId: characteristicId
524
+ ) else {
525
+ return false
526
+ }
527
+ return characteristic.isNotifying
528
+ }
529
+
468
530
  // MARK: - Helper Methods
469
531
  private func findPeripheral(by deviceId: String) -> CBPeripheral? {
470
532
  ensureCentralManager()
@@ -657,7 +719,7 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
657
719
  rssi: Double
658
720
  ) -> BLEDevice {
659
721
  let deviceId = peripheral.identifier.uuidString
660
- let deviceName = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? "Unknown"
722
+ let deviceName = advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? peripheral.name ?? "Unknown"
661
723
 
662
724
  // Extract service UUIDs
663
725
  let serviceUUIDs = (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID])?.map { $0.uuidString } ?? []
@@ -18,7 +18,14 @@ class BlePeripheralDelegate: NSObject, CBPeripheralDelegate {
18
18
  var disconnectEventCallback: ((String, Bool, String) -> Void)?
19
19
  var serviceDiscoveryCallback: ((Bool, String) -> Void)?
20
20
  var characteristicDiscoveryCallbacks: [String: (Bool, String) -> Void] = [:]
21
-
21
+
22
+ // Full discovery (services + characteristics) callbacks and state
23
+ var fullDiscoveryCallbacks: [(Bool, String) -> Void] = []
24
+ var servicesDiscovered = false
25
+ var characteristicsDiscoveredCount = 0
26
+ var expectedCharacteristicsCount = 0
27
+ var characteristicDiscoveryError: String?
28
+
22
29
  // RSSI callback
23
30
  var rssiCallback: ((Bool, Double, String) -> Void)?
24
31
 
@@ -45,15 +52,34 @@ class BlePeripheralDelegate: NSObject, CBPeripheralDelegate {
45
52
  func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
46
53
  if let error = error {
47
54
  serviceDiscoveryCallback?(false, error.localizedDescription)
55
+ serviceDiscoveryCallback = nil
56
+ resolveFullDiscoveryCallbacks(success: false, error: error.localizedDescription)
57
+ return
58
+ }
59
+
60
+ servicesDiscovered = true
61
+
62
+ // Resolve service-only callback immediately
63
+ serviceDiscoveryCallback?(true, "")
64
+ serviceDiscoveryCallback = nil
65
+
66
+ // Trigger characteristic discovery only for services that still need it.
67
+ // CoreBluetooth may skip the didDiscoverCharacteristicsFor callback for
68
+ // services whose characteristics are already cached, which would leave the
69
+ // counter stuck below expectedCharacteristicsCount.
70
+ let services = peripheral.services ?? []
71
+ let undiscovered = services.filter { $0.characteristics == nil }
72
+
73
+ if undiscovered.isEmpty {
74
+ resolveFullDiscoveryCallbacks(success: true, error: "")
48
75
  } else {
49
- serviceDiscoveryCallback?(true, "")
50
-
51
- // Automatically discover characteristics for all services
52
- peripheral.services?.forEach { service in
76
+ expectedCharacteristicsCount = undiscovered.count
77
+ characteristicsDiscoveredCount = 0
78
+ characteristicDiscoveryError = nil
79
+ for service in undiscovered {
53
80
  peripheral.discoverCharacteristics(nil, for: service)
54
81
  }
55
82
  }
56
- serviceDiscoveryCallback = nil
57
83
  }
58
84
 
59
85
  func peripheral(
@@ -62,13 +88,27 @@ class BlePeripheralDelegate: NSObject, CBPeripheralDelegate {
62
88
  error: Error?
63
89
  ) {
64
90
  let serviceId = service.uuid.uuidString
65
-
91
+
66
92
  if let error = error {
67
93
  characteristicDiscoveryCallbacks[serviceId]?(false, error.localizedDescription)
94
+ // Record the first error for the full discovery callback
95
+ if characteristicDiscoveryError == nil {
96
+ characteristicDiscoveryError = "Characteristic discovery failed for service \(serviceId): \(error.localizedDescription)"
97
+ }
68
98
  } else {
69
99
  characteristicDiscoveryCallbacks[serviceId]?(true, "")
70
100
  }
71
101
  characteristicDiscoveryCallbacks.removeValue(forKey: serviceId)
102
+
103
+ // Track full discovery progress
104
+ characteristicsDiscoveredCount += 1
105
+ if characteristicsDiscoveredCount >= expectedCharacteristicsCount {
106
+ if let discoveryError = characteristicDiscoveryError {
107
+ resolveFullDiscoveryCallbacks(success: false, error: discoveryError)
108
+ } else {
109
+ resolveFullDiscoveryCallbacks(success: true, error: "")
110
+ }
111
+ }
72
112
  }
73
113
 
74
114
  // MARK: - CBPeripheralDelegate - Characteristic Read
@@ -197,10 +237,23 @@ class BlePeripheralDelegate: NSObject, CBPeripheralDelegate {
197
237
  // MARK: - CBPeripheralDelegate - Connection Events
198
238
 
199
239
  func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
200
- // Handle service modifications
201
- // This might require re-discovery of services
240
+ resolveFullDiscoveryCallbacks(success: false, error: "Services were invalidated during discovery")
241
+ servicesDiscovered = false
242
+ characteristicsDiscoveredCount = 0
243
+ expectedCharacteristicsCount = 0
244
+ characteristicDiscoveryError = nil
202
245
  }
203
246
 
247
+ // MARK: - Full Discovery Helpers
248
+
249
+ private func resolveFullDiscoveryCallbacks(success: Bool, error: String) {
250
+ let callbacks = fullDiscoveryCallbacks
251
+ fullDiscoveryCallbacks.removeAll()
252
+ for callback in callbacks {
253
+ callback(success, error)
254
+ }
255
+ }
256
+
204
257
  func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
205
258
  // Handle write without response ready state
206
259
  // Can be used to implement queuing for write operations
@@ -208,11 +261,17 @@ class BlePeripheralDelegate: NSObject, CBPeripheralDelegate {
208
261
 
209
262
  // MARK: - Cleanup
210
263
  func cleanup() {
264
+ // Reject in-flight discovery callbacks before clearing
265
+ resolveFullDiscoveryCallbacks(success: false, error: "Peripheral disconnected during discovery")
211
266
  // Clear all callbacks to prevent memory leaks
212
267
  connectionCallback = nil
213
268
  disconnectionCallback = nil
214
269
  disconnectEventCallback = nil
215
270
  serviceDiscoveryCallback = nil
271
+ servicesDiscovered = false
272
+ characteristicsDiscoveredCount = 0
273
+ expectedCharacteristicsCount = 0
274
+ characteristicDiscoveryError = nil
216
275
  characteristicDiscoveryCallbacks.removeAll()
217
276
  rssiCallback = nil
218
277
  readCallbacks.removeAll()
@@ -1,3 +1,3 @@
1
- export { type ByteArray, type ScanFilter, type BLEDevice, type ScanCallback, type ManufacturerDataEntry, type ManufacturerData, type ConnectionCallback, type DisconnectEventCallback, type OperationCallback, type CharacteristicUpdateCallback, type Subscription, type AsyncSubscription, type BleNitroManagerOptions, BLEState, AndroidScanMode, BleNitroManager, } from "./manager";
1
+ export { type ByteArray, type ScanFilter, type BLEDevice, type ScanCallback, type ManufacturerDataEntry, type ManufacturerData, type ConnectionCallback, type DisconnectEventCallback, type OperationCallback, type CharacteristicUpdateCallback, type Subscription, type AsyncSubscription, type BleNitroManagerOptions, BLEState, AndroidScanMode, BleNitroManager, BleTimeoutError, } from "./manager";
2
2
  export { BleNitro } from './singleton';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,QAAQ,EACR,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,QAAQ,EACR,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BleNitro = exports.BleNitroManager = exports.AndroidScanMode = exports.BLEState = void 0;
3
+ exports.BleNitro = exports.BleTimeoutError = exports.BleNitroManager = exports.AndroidScanMode = exports.BLEState = void 0;
4
4
  var manager_1 = require("./manager");
5
5
  Object.defineProperty(exports, "BLEState", { enumerable: true, get: function () { return manager_1.BLEState; } });
6
6
  Object.defineProperty(exports, "AndroidScanMode", { enumerable: true, get: function () { return manager_1.AndroidScanMode; } });
7
7
  Object.defineProperty(exports, "BleNitroManager", { enumerable: true, get: function () { return manager_1.BleNitroManager; } });
8
+ Object.defineProperty(exports, "BleTimeoutError", { enumerable: true, get: function () { return manager_1.BleTimeoutError; } });
8
9
  var singleton_1 = require("./singleton");
9
10
  Object.defineProperty(exports, "BleNitro", { enumerable: true, get: function () { return singleton_1.BleNitro; } });
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAiBmB;AAHjB,mGAAA,QAAQ,OAAA;AACR,0GAAA,eAAe,OAAA;AACf,0GAAA,eAAe,OAAA;AAGjB,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAkBmB;AAJjB,mGAAA,QAAQ,OAAA;AACR,0GAAA,eAAe,OAAA;AACf,0GAAA,eAAe,OAAA;AACf,0GAAA,eAAe,OAAA;AAGjB,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA"}
@@ -1,4 +1,7 @@
1
1
  import { BLEDevice as NativeBLEDevice, BLEState as NativeBLEState, AndroidScanMode as NativeAndroidScanMode } from './specs/NativeBleNitro';
2
+ export declare class BleTimeoutError extends Error {
3
+ constructor(operation: string, ms: number);
4
+ }
2
5
  export type ByteArray = number[];
3
6
  export interface ScanFilter {
4
7
  serviceUUIDs?: string[];
@@ -173,7 +176,10 @@ export declare class BleNitroManager {
173
176
  */
174
177
  getCharacteristics(deviceId: string, serviceId: string): string[];
175
178
  /**
176
- * Get services and characteristics for a connected device
179
+ * Get services and characteristics for a connected device.
180
+ * Uses a native method that waits for both service and characteristic
181
+ * discovery to complete before reading, avoiding the CoreBluetooth race
182
+ * where didDiscoverServices may not re-fire for cached services.
177
183
  * @param deviceId ID of the device
178
184
  * @returns Promise resolving to array of service and characteristic UUIDs
179
185
  * @see getServices
@@ -183,6 +189,8 @@ export declare class BleNitroManager {
183
189
  uuid: string;
184
190
  characteristics: string[];
185
191
  }[]>;
192
+ private static readonly DISCOVERY_TIMEOUT_MS;
193
+ private _discoverServicesWithCharacteristics;
186
194
  /**
187
195
  * Read a characteristic value
188
196
  * @param deviceId ID of the device
@@ -218,6 +226,14 @@ export declare class BleNitroManager {
218
226
  * @returns Promise resolving when unsubscription is complete
219
227
  */
220
228
  unsubscribeFromCharacteristic(deviceId: string, serviceId: string, characteristicId: string): Promise<void>;
229
+ /**
230
+ * Check if currently subscribed to a characteristic's notifications
231
+ * @param deviceId ID of the device
232
+ * @param serviceId ID of the service
233
+ * @param characteristicId ID of the characteristic
234
+ * @returns Boolean indicating if subscribed to notifications
235
+ */
236
+ isSubscribedToCharacteristic(deviceId: string, serviceId: string, characteristicId: string): boolean;
221
237
  /**
222
238
  * Check if Bluetooth is enabled
223
239
  * @returns returns Boolean according to Bluetooth state
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,SAAS,IAAI,eAAe,EAC5B,QAAQ,IAAI,cAAc,EAE1B,eAAe,IAAI,qBAAqB,EACzC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC;AAEjC,MAAM,WAAW,UAAU;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB,EAAE,qBAAqB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;AACvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,oBAAoB,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;AAC/E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;AACV,MAAM,MAAM,uBAAuB,GAAG,CACpC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,OAAO,EACpB,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;AACV,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAC1E,MAAM,MAAM,4BAA4B,GAAG,CACzC,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,SAAS,KACZ,IAAI,CAAC;AAEV,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B,CAAA;AAED,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,UAAU,eAAe;IACzB,SAAS,cAAc;CACxB;AAED,oBAAY,eAAe;IACzB,UAAU,eAAe;IACzB,QAAQ,aAAa;IACrB,QAAQ,aAAa;IACrB,aAAa,kBAAkB;CAChC;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,oBAAoB,CAAC;CACxC,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,cAAc,GAAG,QAAQ,CAUjF;AAED,wBAAgB,yCAAyC,CAAC,QAAQ,EAAE,eAAe,GAAG,qBAAqB,CAQ1G;AAED,wBAAgB,iCAAiC,CAAC,eAAe,EAAE,eAAe,GAAG,SAAS,CAW7F;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAErE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,WAAW,CAEnE;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAkB;IAErC,OAAO,CAAC,sBAAsB,CAA8B;IAC5D,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,uBAAuB,CAAuB;IAEtD,OAAO,CAAC,QAAQ,CAAiB;gBAErB,OAAO,CAAC,EAAE,sBAAsB;IAM5C,OAAO,CAAC,4BAA4B;IAUpC;;;;;OAKG;IACI,WAAW,IAAI,IAAI;IAI1B;;;;OAIG;IACI,eAAe,CAAC,QAAQ,EAAE,oBAAoB;IASrD;;;;;OAKG;WACW,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;WAcvC,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAI3D;;;;;OAKG;IACI,SAAS,CACd,MAAM,EAAE,UAAU,YAAK,EACvB,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAChC,IAAI;IA+BP;;;OAGG;IACI,QAAQ,IAAI,IAAI;IASvB;;;OAGG;IACI,UAAU,IAAI,OAAO;IAK5B;;;;OAIG;IACI,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE;IAM5D;;;;;OAKG;IACI,OAAO,CACZ,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,uBAAuB,EACtC,kBAAkB,CAAC,EAAE,OAAO,GAC3B,OAAO,CAAC,MAAM,CAAC;IAyBlB;;;;;OAKG;IACI,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IA2BzM;;;;OAIG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBpD;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI7C;;;;;OAKG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAMxD;;;;OAIG;IACI,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBlD;;;;OAIG;IACI,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqB3D;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAkBvD;;;;;OAKG;IACI,kBAAkB,CACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,EAAE;IAYX;;;;;;OAMG;IACU,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAWrH;;;;;;OAMG;IACI,kBAAkB,CACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,SAAS,CAAC;IAuBrB;;;;;;;;OAQG;IACI,mBAAmB,CACxB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,SAAS,EACf,YAAY,GAAE,OAAc,GAC3B,OAAO,CAAC,SAAS,CAAC;IA2BrB;;;;;;;OAOG;IACU,yBAAyB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,4BAA4B,GACrC,OAAO,CAAC,iBAAiB,CAAC;IAqC7B;;;;;;OAMG;IACI,6BAA6B,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;IAuBhB;;;OAGG;IACI,kBAAkB,IAAI,OAAO;IAIpC;;;OAGG;IACI,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC;IAcjD;;;;OAIG;IACI,KAAK,IAAI,QAAQ;IAIxB;;;;;;OAMG;IACI,sBAAsB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,EAAE,WAAW,UAAQ,GAAG,YAAY;IAiBrG;;;OAGG;IACI,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,SAAS,IAAI,eAAe,EAC5B,QAAQ,IAAI,cAAc,EAE1B,eAAe,IAAI,qBAAqB,EACzC,MAAM,wBAAwB,CAAC;AAEhC,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;CAI1C;AAcD,MAAM,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC;AAEjC,MAAM,WAAW,UAAU;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB,EAAE,qBAAqB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;AACvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,oBAAoB,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;AAC/E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;AACV,MAAM,MAAM,uBAAuB,GAAG,CACpC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,OAAO,EACpB,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;AACV,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAC1E,MAAM,MAAM,4BAA4B,GAAG,CACzC,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,SAAS,KACZ,IAAI,CAAC;AAEV,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B,CAAA;AAED,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAC7B,UAAU,eAAe;IACzB,SAAS,cAAc;CACxB;AAED,oBAAY,eAAe;IACzB,UAAU,eAAe;IACzB,QAAQ,aAAa;IACrB,QAAQ,aAAa;IACrB,aAAa,kBAAkB;CAChC;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,oBAAoB,CAAC;CACxC,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,cAAc,GAAG,QAAQ,CAUjF;AAED,wBAAgB,yCAAyC,CAAC,QAAQ,EAAE,eAAe,GAAG,qBAAqB,CAQ1G;AAED,wBAAgB,iCAAiC,CAAC,eAAe,EAAE,eAAe,GAAG,SAAS,CAW7F;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAErE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,WAAW,CAEnE;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAkB;IAErC,OAAO,CAAC,sBAAsB,CAA8B;IAC5D,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,uBAAuB,CAAuB;IAEtD,OAAO,CAAC,QAAQ,CAAiB;gBAErB,OAAO,CAAC,EAAE,sBAAsB;IAM5C,OAAO,CAAC,4BAA4B;IAUpC;;;;;OAKG;IACI,WAAW,IAAI,IAAI;IAI1B;;;;OAIG;IACI,eAAe,CAAC,QAAQ,EAAE,oBAAoB;IASrD;;;;;OAKG;WACW,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;WAcvC,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAI3D;;;;;OAKG;IACI,SAAS,CACd,MAAM,EAAE,UAAU,YAAK,EACvB,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAChC,IAAI;IA+BP;;;OAGG;IACI,QAAQ,IAAI,IAAI;IASvB;;;OAGG;IACI,UAAU,IAAI,OAAO;IAK5B;;;;OAIG;IACI,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE;IAM5D;;;;;OAKG;IACI,OAAO,CACZ,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,uBAAuB,EACtC,kBAAkB,CAAC,EAAE,OAAO,GAC3B,OAAO,CAAC,MAAM,CAAC;IAyBlB;;;;;OAKG;IACI,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IA2BzM;;;;OAIG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBpD;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI7C;;;;;OAKG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;IAMxD;;;;OAIG;IACI,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBlD;;;;OAIG;IACI,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqB3D;;;;OAIG;IACI,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAkBvD;;;;;OAKG;IACI,kBAAkB,CACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,EAAE;IAYX;;;;;;;;;OASG;IACU,8BAA8B,CACzC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAUzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAU;IAEtD,OAAO,CAAC,oCAAoC;IA0B5C;;;;;;OAMG;IACI,kBAAkB,CACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,SAAS,CAAC;IAuBrB;;;;;;;;OAQG;IACI,mBAAmB,CACxB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,SAAS,EACf,YAAY,GAAE,OAAc,GAC3B,OAAO,CAAC,SAAS,CAAC;IA2BrB;;;;;;;OAOG;IACU,yBAAyB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,4BAA4B,GACrC,OAAO,CAAC,iBAAiB,CAAC;IAqC7B;;;;;;OAMG;IACI,6BAA6B,CAClC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;IAuBhB;;;;;;OAMG;IACI,4BAA4B,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO;IAUV;;;OAGG;IACI,kBAAkB,IAAI,OAAO;IAIpC;;;OAGG;IACI,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC;IAcjD;;;;OAIG;IACI,KAAK,IAAI,QAAQ;IAIxB;;;;;;OAMG;IACI,sBAAsB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,EAAE,WAAW,UAAQ,GAAG,YAAY;IAiBrG;;;OAGG;IACI,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.BleNitroManager = exports.AndroidScanMode = exports.BLEState = void 0;
6
+ exports.BleNitroManager = exports.AndroidScanMode = exports.BLEState = exports.BleTimeoutError = void 0;
7
7
  exports.mapNativeBLEStateToBLEState = mapNativeBLEStateToBLEState;
8
8
  exports.mapAndroidScanModeToNativeAndroidScanMode = mapAndroidScanModeToNativeAndroidScanMode;
9
9
  exports.convertNativeBleDeviceToBleDevice = convertNativeBleDeviceToBleDevice;
@@ -11,6 +11,20 @@ exports.arrayBufferToByteArray = arrayBufferToByteArray;
11
11
  exports.byteArrayToArrayBuffer = byteArrayToArrayBuffer;
12
12
  const NativeBleNitroFactory_1 = __importDefault(require("./specs/NativeBleNitroFactory"));
13
13
  const NativeBleNitro_1 = require("./specs/NativeBleNitro");
14
+ class BleTimeoutError extends Error {
15
+ constructor(operation, ms) {
16
+ super(`${operation} timed out after ${ms}ms`);
17
+ this.name = 'BleTimeoutError';
18
+ }
19
+ }
20
+ exports.BleTimeoutError = BleTimeoutError;
21
+ function withTimeout(promise, ms, operation) {
22
+ let timer;
23
+ const timeout = new Promise((_, reject) => {
24
+ timer = setTimeout(() => reject(new BleTimeoutError(operation, ms)), ms);
25
+ });
26
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
27
+ }
14
28
  var BLEState;
15
29
  (function (BLEState) {
16
30
  BLEState["Unknown"] = "Unknown";
@@ -372,21 +386,39 @@ class BleNitroManager {
372
386
  return BleNitroManager.normalizeGattUUIDs(characteristics);
373
387
  }
374
388
  /**
375
- * Get services and characteristics for a connected device
389
+ * Get services and characteristics for a connected device.
390
+ * Uses a native method that waits for both service and characteristic
391
+ * discovery to complete before reading, avoiding the CoreBluetooth race
392
+ * where didDiscoverServices may not re-fire for cached services.
376
393
  * @param deviceId ID of the device
377
394
  * @returns Promise resolving to array of service and characteristic UUIDs
378
395
  * @see getServices
379
396
  * @see getCharacteristics
380
397
  */
381
398
  async getServicesWithCharacteristics(deviceId) {
382
- await this.discoverServices(deviceId);
383
- const services = await this.getServices(deviceId);
384
- return services.map((service) => {
385
- return {
386
- uuid: service,
387
- characteristics: this.getCharacteristics(deviceId, service),
388
- };
399
+ await this._discoverServicesWithCharacteristics(deviceId);
400
+ const services = this.Instance.getServices(deviceId);
401
+ return BleNitroManager.normalizeGattUUIDs(services).map((service) => ({
402
+ uuid: service,
403
+ characteristics: this.getCharacteristics(deviceId, service),
404
+ }));
405
+ }
406
+ _discoverServicesWithCharacteristics(deviceId) {
407
+ const inner = new Promise((resolve, reject) => {
408
+ if (!this.isConnected(deviceId)) {
409
+ reject(new Error('Device not connected'));
410
+ return;
411
+ }
412
+ this.Instance.discoverServicesWithCharacteristics(deviceId, (success, error) => {
413
+ if (success) {
414
+ resolve();
415
+ }
416
+ else {
417
+ reject(new Error(error));
418
+ }
419
+ });
389
420
  });
421
+ return withTimeout(inner, BleNitroManager.DISCOVERY_TIMEOUT_MS, 'discoverServicesWithCharacteristics');
390
422
  }
391
423
  /**
392
424
  * Read a characteristic value
@@ -495,6 +527,18 @@ class BleNitroManager {
495
527
  });
496
528
  });
497
529
  }
530
+ /**
531
+ * Check if currently subscribed to a characteristic's notifications
532
+ * @param deviceId ID of the device
533
+ * @param serviceId ID of the service
534
+ * @param characteristicId ID of the characteristic
535
+ * @returns Boolean indicating if subscribed to notifications
536
+ */
537
+ isSubscribedToCharacteristic(deviceId, serviceId, characteristicId) {
538
+ // No isConnected guard — both native implementations already return false
539
+ // for disconnected devices, and an extra check would introduce a TOCTOU race.
540
+ return this.Instance.isSubscribedToCharacteristic(deviceId, BleNitroManager.normalizeGattUUID(serviceId), BleNitroManager.normalizeGattUUID(characteristicId));
541
+ }
498
542
  /**
499
543
  * Check if Bluetooth is enabled
500
544
  * @returns returns Boolean according to Bluetooth state
@@ -556,4 +600,5 @@ class BleNitroManager {
556
600
  }
557
601
  }
558
602
  exports.BleNitroManager = BleNitroManager;
603
+ BleNitroManager.DISCOVERY_TIMEOUT_MS = 30000;
559
604
  //# sourceMappingURL=manager.js.map