react-native-ble-nitro 1.11.0 → 1.12.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 (31) hide show
  1. package/android/src/main/java/com/margelo/nitro/co/zyke/ble/BleNitroBleManager.kt +58 -21
  2. package/ios/BleNitroBleManager.swift +60 -3
  3. package/ios/BlePeripheralDelegate.swift +68 -9
  4. package/lib/commonjs/index.d.ts +1 -1
  5. package/lib/commonjs/index.d.ts.map +1 -1
  6. package/lib/commonjs/index.js +2 -1
  7. package/lib/commonjs/index.js.map +1 -1
  8. package/lib/commonjs/manager.d.ts +17 -1
  9. package/lib/commonjs/manager.d.ts.map +1 -1
  10. package/lib/commonjs/manager.js +54 -9
  11. package/lib/commonjs/manager.js.map +1 -1
  12. package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts +3 -0
  13. package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts.map +1 -1
  14. package/lib/index.d.ts +1 -1
  15. package/lib/index.js +1 -1
  16. package/lib/manager.d.ts +17 -1
  17. package/lib/manager.js +52 -8
  18. package/lib/specs/NativeBleNitro.nitro.d.ts +3 -0
  19. package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.cpp +9 -0
  20. package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.hpp +2 -0
  21. package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/HybridNativeBleNitroSpec.kt +13 -0
  22. package/nitrogen/generated/ios/c++/HybridNativeBleNitroSpecSwift.hpp +14 -0
  23. package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec.swift +2 -0
  24. package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec_cxx.swift +28 -0
  25. package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.cpp +2 -0
  26. package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.hpp +2 -0
  27. package/package.json +1 -1
  28. package/src/__tests__/index.test.ts +127 -0
  29. package/src/index.ts +1 -0
  30. package/src/manager.ts +81 -9
  31. package/src/specs/NativeBleNitro.nitro.ts +3 -0
package/src/manager.ts CHANGED
@@ -7,6 +7,25 @@ import {
7
7
  AndroidScanMode as NativeAndroidScanMode,
8
8
  } from './specs/NativeBleNitro';
9
9
 
10
+ export class BleTimeoutError extends Error {
11
+ constructor(operation: string, ms: number) {
12
+ super(`${operation} timed out after ${ms}ms`);
13
+ this.name = 'BleTimeoutError';
14
+ }
15
+ }
16
+
17
+ function withTimeout<T>(
18
+ promise: Promise<T>,
19
+ ms: number,
20
+ operation: string
21
+ ): Promise<T> {
22
+ let timer: ReturnType<typeof setTimeout>;
23
+ const timeout = new Promise<never>((_, reject) => {
24
+ timer = setTimeout(() => reject(new BleTimeoutError(operation, ms)), ms);
25
+ });
26
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
27
+ }
28
+
10
29
  export type ByteArray = number[];
11
30
 
12
31
  export interface ScanFilter {
@@ -484,21 +503,53 @@ export class BleNitroManager {
484
503
  }
485
504
 
486
505
  /**
487
- * Get services and characteristics for a connected device
506
+ * Get services and characteristics for a connected device.
507
+ * Uses a native method that waits for both service and characteristic
508
+ * discovery to complete before reading, avoiding the CoreBluetooth race
509
+ * where didDiscoverServices may not re-fire for cached services.
488
510
  * @param deviceId ID of the device
489
511
  * @returns Promise resolving to array of service and characteristic UUIDs
490
512
  * @see getServices
491
513
  * @see getCharacteristics
492
514
  */
493
- public async getServicesWithCharacteristics(deviceId: string): Promise<{ uuid: string; characteristics: string[] }[]> {
494
- await this.discoverServices(deviceId);
495
- const services = await this.getServices(deviceId);
496
- return services.map((service) => {
497
- return {
498
- uuid: service,
499
- characteristics: this.getCharacteristics(deviceId, service),
500
- };
515
+ public async getServicesWithCharacteristics(
516
+ deviceId: string
517
+ ): Promise<{ uuid: string; characteristics: string[] }[]> {
518
+ await this._discoverServicesWithCharacteristics(deviceId);
519
+
520
+ const services = this.Instance.getServices(deviceId);
521
+ return BleNitroManager.normalizeGattUUIDs(services).map((service) => ({
522
+ uuid: service,
523
+ characteristics: this.getCharacteristics(deviceId, service),
524
+ }));
525
+ }
526
+
527
+ private static readonly DISCOVERY_TIMEOUT_MS = 30_000;
528
+
529
+ private _discoverServicesWithCharacteristics(
530
+ deviceId: string
531
+ ): Promise<void> {
532
+ const inner = new Promise<void>((resolve, reject) => {
533
+ if (!this.isConnected(deviceId)) {
534
+ reject(new Error('Device not connected'));
535
+ return;
536
+ }
537
+ this.Instance.discoverServicesWithCharacteristics(
538
+ deviceId,
539
+ (success: boolean, error: string) => {
540
+ if (success) {
541
+ resolve();
542
+ } else {
543
+ reject(new Error(error));
544
+ }
545
+ }
546
+ );
501
547
  });
548
+ return withTimeout(
549
+ inner,
550
+ BleNitroManager.DISCOVERY_TIMEOUT_MS,
551
+ 'discoverServicesWithCharacteristics'
552
+ );
502
553
  }
503
554
 
504
555
  /**
@@ -661,6 +712,27 @@ export class BleNitroManager {
661
712
  });
662
713
  }
663
714
 
715
+ /**
716
+ * Check if currently subscribed to a characteristic's notifications
717
+ * @param deviceId ID of the device
718
+ * @param serviceId ID of the service
719
+ * @param characteristicId ID of the characteristic
720
+ * @returns Boolean indicating if subscribed to notifications
721
+ */
722
+ public isSubscribedToCharacteristic(
723
+ deviceId: string,
724
+ serviceId: string,
725
+ characteristicId: string
726
+ ): boolean {
727
+ // No isConnected guard — both native implementations already return false
728
+ // for disconnected devices, and an extra check would introduce a TOCTOU race.
729
+ return this.Instance.isSubscribedToCharacteristic(
730
+ deviceId,
731
+ BleNitroManager.normalizeGattUUID(serviceId),
732
+ BleNitroManager.normalizeGattUUID(characteristicId)
733
+ );
734
+ }
735
+
664
736
  /**
665
737
  * Check if Bluetooth is enabled
666
738
  * @returns returns Boolean according to Bluetooth state
@@ -92,6 +92,8 @@ export interface NativeBleNitro extends HybridObject<{ ios: 'swift'; android: 'k
92
92
 
93
93
  // Service discovery
94
94
  discoverServices(deviceId: string, callback: OperationCallback): void;
95
+ /** Discover services and wait for all characteristic discovery to complete before resolving. */
96
+ discoverServicesWithCharacteristics(deviceId: string, callback: OperationCallback): void;
95
97
  getServices(deviceId: string): string[];
96
98
  getCharacteristics(deviceId: string, serviceId: string): string[];
97
99
 
@@ -100,6 +102,7 @@ export interface NativeBleNitro extends HybridObject<{ ios: 'swift'; android: 'k
100
102
  writeCharacteristic(deviceId: string, serviceId: string, characteristicId: string, data: BLEValue, withResponse: boolean, callback: WriteCharacteristicCallback): void;
101
103
  subscribeToCharacteristic(deviceId: string, serviceId: string, characteristicId: string, updateCallback: CharacteristicCallback, completionCallback: OperationCallback): void;
102
104
  unsubscribeFromCharacteristic(deviceId: string, serviceId: string, characteristicId: string, callback: OperationCallback): void;
105
+ isSubscribedToCharacteristic(deviceId: string, serviceId: string, characteristicId: string): boolean;
103
106
 
104
107
  // Bluetooth state management
105
108
  requestBluetoothEnable(callback: OperationCallback): void;