react-native-ble-nitro 1.10.3 → 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.
- package/android/src/main/cpp/cpp-adapter.cpp +4 -1
- package/android/src/main/java/com/margelo/nitro/co/zyke/ble/BleNitroBleManager.kt +58 -21
- package/ios/BleNitroBleManager.swift +60 -3
- package/ios/BlePeripheralDelegate.swift +68 -9
- package/lib/commonjs/index.d.ts +1 -1
- package/lib/commonjs/index.d.ts.map +1 -1
- package/lib/commonjs/index.js +2 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/manager.d.ts +19 -4
- package/lib/commonjs/manager.d.ts.map +1 -1
- package/lib/commonjs/manager.js +68 -33
- package/lib/commonjs/manager.js.map +1 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts +3 -0
- package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/manager.d.ts +19 -4
- package/lib/manager.js +66 -32
- package/lib/specs/NativeBleNitro.nitro.d.ts +3 -0
- package/nitrogen/generated/android/BleNitroOnLoad.cpp +48 -32
- package/nitrogen/generated/android/BleNitroOnLoad.hpp +13 -4
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroFactorySpec.cpp +20 -26
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroFactorySpec.hpp +19 -22
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.cpp +52 -49
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.hpp +21 -22
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/HybridNativeBleNitroFactorySpec.kt +15 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/HybridNativeBleNitroSpec.kt +28 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/Variant_NullType_BLEDevice.kt +0 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/Variant_NullType_String.kt +0 -6
- package/nitrogen/generated/ios/c++/HybridNativeBleNitroSpecSwift.hpp +14 -0
- package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec_cxx.swift +28 -0
- package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.hpp +2 -0
- package/package.json +9 -9
- package/src/__tests__/index.test.ts +145 -1
- package/src/index.ts +1 -0
- package/src/manager.ts +96 -34
- 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 {
|
|
@@ -127,7 +146,6 @@ export function byteArrayToArrayBuffer(data: ByteArray): ArrayBuffer {
|
|
|
127
146
|
|
|
128
147
|
export class BleNitroManager {
|
|
129
148
|
private _isScanning: boolean = false;
|
|
130
|
-
private _connectedDevices: { [deviceId: string]: boolean } = {};
|
|
131
149
|
|
|
132
150
|
private _restoredStateCallback: RestoreStateCallback | null;
|
|
133
151
|
private _restoredState: BLEDevice[] | null = null;
|
|
@@ -144,9 +162,6 @@ export class BleNitroManager {
|
|
|
144
162
|
private onNativeRestoreStateCallback(peripherals: NativeBLEDevice[]) {
|
|
145
163
|
if (!this._restoreStateIdentifier) return;
|
|
146
164
|
const bleDevices = peripherals.map((peripheral) => convertNativeBleDeviceToBleDevice(peripheral));
|
|
147
|
-
bleDevices.forEach((device) => {
|
|
148
|
-
this._connectedDevices[device.id] = device.isConnected;
|
|
149
|
-
});
|
|
150
165
|
if (this._restoredStateCallback) {
|
|
151
166
|
this._restoredStateCallback(bleDevices);
|
|
152
167
|
} else {
|
|
@@ -289,7 +304,7 @@ export class BleNitroManager {
|
|
|
289
304
|
): Promise<string> {
|
|
290
305
|
return new Promise((resolve, reject) => {
|
|
291
306
|
// Check if already connected
|
|
292
|
-
if (this.
|
|
307
|
+
if (this.isConnected(deviceId)) {
|
|
293
308
|
resolve(deviceId);
|
|
294
309
|
return;
|
|
295
310
|
}
|
|
@@ -298,15 +313,12 @@ export class BleNitroManager {
|
|
|
298
313
|
deviceId,
|
|
299
314
|
(success: boolean, connectedDeviceId: string, error: string) => {
|
|
300
315
|
if (success) {
|
|
301
|
-
this._connectedDevices[deviceId] = true;
|
|
302
316
|
resolve(connectedDeviceId);
|
|
303
317
|
} else {
|
|
304
318
|
reject(new Error(error));
|
|
305
319
|
}
|
|
306
320
|
},
|
|
307
321
|
onDisconnect ? (deviceId: string, interrupted: boolean, error: string) => {
|
|
308
|
-
// Remove from connected devices when disconnected
|
|
309
|
-
delete this._connectedDevices[deviceId];
|
|
310
322
|
onDisconnect(deviceId, interrupted, error);
|
|
311
323
|
} : undefined,
|
|
312
324
|
autoConnectAndroid ?? false,
|
|
@@ -321,8 +333,7 @@ export class BleNitroManager {
|
|
|
321
333
|
* @returns Promise resolving deviceId when connected
|
|
322
334
|
*/
|
|
323
335
|
public findAndConnect(deviceId: string, options?: { scanTimeout?: number, autoConnectAndroid?: boolean, onDisconnect?: DisconnectEventCallback, onFound?: (device: BLEDevice) => void }): Promise<string> {
|
|
324
|
-
|
|
325
|
-
if (isConnected) {
|
|
336
|
+
if (this.isConnected(deviceId)) {
|
|
326
337
|
return Promise.resolve(deviceId);
|
|
327
338
|
}
|
|
328
339
|
if (this._isScanning) {
|
|
@@ -351,14 +362,13 @@ export class BleNitroManager {
|
|
|
351
362
|
/**
|
|
352
363
|
* Disconnect from a Bluetooth device
|
|
353
364
|
* @param deviceId ID of the device to disconnect from
|
|
354
|
-
* @returns Promise resolving when disconnected
|
|
365
|
+
* @returns Promise resolving with devices uuid or mac address when disconnected
|
|
355
366
|
*/
|
|
356
|
-
public disconnect(deviceId: string): Promise<
|
|
367
|
+
public disconnect(deviceId: string): Promise<string> {
|
|
357
368
|
return new Promise((resolve, reject) => {
|
|
358
369
|
// Check if already disconnected
|
|
359
|
-
if (!this.
|
|
360
|
-
|
|
361
|
-
resolve();
|
|
370
|
+
if (!this.isConnected(deviceId)) {
|
|
371
|
+
resolve(deviceId);
|
|
362
372
|
return;
|
|
363
373
|
}
|
|
364
374
|
|
|
@@ -366,8 +376,7 @@ export class BleNitroManager {
|
|
|
366
376
|
deviceId,
|
|
367
377
|
(success: boolean, error: string) => {
|
|
368
378
|
if (success) {
|
|
369
|
-
|
|
370
|
-
resolve();
|
|
379
|
+
resolve(deviceId);
|
|
371
380
|
} else {
|
|
372
381
|
reject(new Error(error));
|
|
373
382
|
}
|
|
@@ -405,7 +414,7 @@ export class BleNitroManager {
|
|
|
405
414
|
public readRSSI(deviceId: string): Promise<number> {
|
|
406
415
|
return new Promise((resolve, reject) => {
|
|
407
416
|
// Check if connected first
|
|
408
|
-
if (!this.
|
|
417
|
+
if (!this.isConnected(deviceId)) {
|
|
409
418
|
reject(new Error('Device not connected'));
|
|
410
419
|
return;
|
|
411
420
|
}
|
|
@@ -431,7 +440,7 @@ export class BleNitroManager {
|
|
|
431
440
|
public discoverServices(deviceId: string): Promise<boolean> {
|
|
432
441
|
return new Promise((resolve, reject) => {
|
|
433
442
|
// Check if connected first
|
|
434
|
-
if (!this.
|
|
443
|
+
if (!this.isConnected(deviceId)) {
|
|
435
444
|
reject(new Error('Device not connected'));
|
|
436
445
|
return;
|
|
437
446
|
}
|
|
@@ -457,7 +466,7 @@ export class BleNitroManager {
|
|
|
457
466
|
public getServices(deviceId: string): Promise<string[]> {
|
|
458
467
|
return new Promise(async (resolve, reject) => {
|
|
459
468
|
// Check if connected first
|
|
460
|
-
if (!this.
|
|
469
|
+
if (!this.isConnected(deviceId)) {
|
|
461
470
|
reject(new Error('Device not connected'));
|
|
462
471
|
return;
|
|
463
472
|
}
|
|
@@ -482,7 +491,7 @@ export class BleNitroManager {
|
|
|
482
491
|
deviceId: string,
|
|
483
492
|
serviceId: string
|
|
484
493
|
): string[] {
|
|
485
|
-
if (!this.
|
|
494
|
+
if (!this.isConnected(deviceId)) {
|
|
486
495
|
throw new Error('Device not connected');
|
|
487
496
|
}
|
|
488
497
|
|
|
@@ -494,21 +503,53 @@ export class BleNitroManager {
|
|
|
494
503
|
}
|
|
495
504
|
|
|
496
505
|
/**
|
|
497
|
-
* 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.
|
|
498
510
|
* @param deviceId ID of the device
|
|
499
511
|
* @returns Promise resolving to array of service and characteristic UUIDs
|
|
500
512
|
* @see getServices
|
|
501
513
|
* @see getCharacteristics
|
|
502
514
|
*/
|
|
503
|
-
public async getServicesWithCharacteristics(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
+
);
|
|
511
547
|
});
|
|
548
|
+
return withTimeout(
|
|
549
|
+
inner,
|
|
550
|
+
BleNitroManager.DISCOVERY_TIMEOUT_MS,
|
|
551
|
+
'discoverServicesWithCharacteristics'
|
|
552
|
+
);
|
|
512
553
|
}
|
|
513
554
|
|
|
514
555
|
/**
|
|
@@ -525,7 +566,7 @@ export class BleNitroManager {
|
|
|
525
566
|
): Promise<ByteArray> {
|
|
526
567
|
return new Promise((resolve, reject) => {
|
|
527
568
|
// Check if connected first
|
|
528
|
-
if (!this.
|
|
569
|
+
if (!this.isConnected(deviceId)) {
|
|
529
570
|
reject(new Error('Device not connected'));
|
|
530
571
|
return;
|
|
531
572
|
}
|
|
@@ -563,7 +604,7 @@ export class BleNitroManager {
|
|
|
563
604
|
): Promise<ByteArray> {
|
|
564
605
|
return new Promise((resolve, reject) => {
|
|
565
606
|
// Check if connected first
|
|
566
|
-
if (!this.
|
|
607
|
+
if (!this.isConnected(deviceId)) {
|
|
567
608
|
reject(new Error('Device not connected'));
|
|
568
609
|
return;
|
|
569
610
|
}
|
|
@@ -603,7 +644,7 @@ export class BleNitroManager {
|
|
|
603
644
|
): Promise<AsyncSubscription> {
|
|
604
645
|
return new Promise((resolve, reject) => {
|
|
605
646
|
// Check if connected first
|
|
606
|
-
if (!this.
|
|
647
|
+
if (!this.isConnected(deviceId)) {
|
|
607
648
|
reject(new Error('Device not connected'));
|
|
608
649
|
return;
|
|
609
650
|
}
|
|
@@ -651,7 +692,7 @@ export class BleNitroManager {
|
|
|
651
692
|
): Promise<void> {
|
|
652
693
|
return new Promise((resolve, reject) => {
|
|
653
694
|
// Check if connected first
|
|
654
|
-
if (!this.
|
|
695
|
+
if (!this.isConnected(deviceId)) {
|
|
655
696
|
reject(new Error('Device not connected'));
|
|
656
697
|
return;
|
|
657
698
|
}
|
|
@@ -671,6 +712,27 @@ export class BleNitroManager {
|
|
|
671
712
|
});
|
|
672
713
|
}
|
|
673
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
|
+
|
|
674
736
|
/**
|
|
675
737
|
* Check if Bluetooth is enabled
|
|
676
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;
|