react-native-ble-nitro 1.2.0 → 1.3.1
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/README.md +89 -28
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +140 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/co/zyke/ble/BleNitroBleManager.kt +899 -0
- package/android/src/main/java/com/margelo/nitro/co/zyke/ble/BleNitroPackage.kt +38 -0
- package/ios/BleNitroBleManager.swift +7 -3
- package/lib/commonjs/index.d.ts +15 -3
- package/lib/commonjs/index.d.ts.map +1 -1
- package/lib/commonjs/index.js +46 -28
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts +8 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts.map +1 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.js +8 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.js.map +1 -1
- package/lib/index.d.ts +15 -3
- package/lib/index.js +44 -26
- package/lib/specs/NativeBleNitro.nitro.d.ts +8 -1
- package/lib/specs/NativeBleNitro.nitro.js +7 -0
- package/nitrogen/generated/android/BleNitroOnLoad.cpp +2 -2
- package/nitrogen/generated/android/c++/JAndroidScanMode.hpp +65 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_BLEDevice__std__optional_std__string_.hpp +86 -0
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.cpp +8 -4
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JScanFilter.hpp +8 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/AndroidScanMode.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/{Func_void_BLEDevice.kt → Func_void_std__optional_BLEDevice__std__optional_std__string_.kt} +14 -14
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/HybridNativeBleNitroSpec.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/ScanFilter.kt +4 -1
- package/nitrogen/generated/ios/BleNitro-Swift-Cxx-Bridge.cpp +5 -5
- package/nitrogen/generated/ios/BleNitro-Swift-Cxx-Bridge.hpp +30 -21
- package/nitrogen/generated/ios/BleNitro-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridNativeBleNitroSpecSwift.hpp +5 -2
- package/nitrogen/generated/ios/swift/AndroidScanMode.swift +48 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_BLEDevice__std__optional_std__string_.swift +59 -0
- package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec_cxx.swift +17 -5
- package/nitrogen/generated/ios/swift/ScanFilter.swift +13 -2
- package/nitrogen/generated/shared/c++/AndroidScanMode.hpp +64 -0
- package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.hpp +3 -3
- package/nitrogen/generated/shared/c++/ScanFilter.hpp +9 -3
- package/package.json +1 -1
- package/plugin/build/index.d.ts +2 -0
- package/plugin/build/index.js +2 -0
- package/plugin/build/withBleNitro.d.ts +5 -1
- package/plugin/build/withBleNitro.js +18 -7
- package/src/__tests__/index.test.ts +45 -11
- package/src/index.ts +55 -27
- package/src/specs/NativeBleNitro.nitro.ts +9 -1
- package/nitrogen/generated/android/c++/JFunc_void_BLEDevice.hpp +0 -85
- package/nitrogen/generated/ios/swift/Func_void_BLEDevice.swift +0 -47
|
@@ -110,12 +110,24 @@ open class HybridNativeBleNitroSpec_cxx {
|
|
|
110
110
|
|
|
111
111
|
// Methods
|
|
112
112
|
@inline(__always)
|
|
113
|
-
public final func startScan(filter: ScanFilter, callback: bridge.
|
|
113
|
+
public final func startScan(filter: ScanFilter, callback: bridge.Func_void_std__optional_BLEDevice__std__optional_std__string_) -> bridge.Result_void_ {
|
|
114
114
|
do {
|
|
115
|
-
try self.__implementation.startScan(filter: filter, callback: { () -> (BLEDevice) -> Void in
|
|
116
|
-
let __wrappedFunction = bridge.
|
|
117
|
-
return { (__device: BLEDevice) -> Void in
|
|
118
|
-
__wrappedFunction.call(
|
|
115
|
+
try self.__implementation.startScan(filter: filter, callback: { () -> (BLEDevice?, String?) -> Void in
|
|
116
|
+
let __wrappedFunction = bridge.wrap_Func_void_std__optional_BLEDevice__std__optional_std__string_(callback)
|
|
117
|
+
return { (__device: BLEDevice?, __error: String?) -> Void in
|
|
118
|
+
__wrappedFunction.call({ () -> bridge.std__optional_BLEDevice_ in
|
|
119
|
+
if let __unwrappedValue = __device {
|
|
120
|
+
return bridge.create_std__optional_BLEDevice_(__unwrappedValue)
|
|
121
|
+
} else {
|
|
122
|
+
return .init()
|
|
123
|
+
}
|
|
124
|
+
}(), { () -> bridge.std__optional_std__string_ in
|
|
125
|
+
if let __unwrappedValue = __error {
|
|
126
|
+
return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
|
|
127
|
+
} else {
|
|
128
|
+
return .init()
|
|
129
|
+
}
|
|
130
|
+
}())
|
|
119
131
|
}
|
|
120
132
|
}())
|
|
121
133
|
return bridge.create_Result_void_()
|
|
@@ -18,14 +18,14 @@ public extension ScanFilter {
|
|
|
18
18
|
/**
|
|
19
19
|
* Create a new instance of `ScanFilter`.
|
|
20
20
|
*/
|
|
21
|
-
init(serviceUUIDs: [String], rssiThreshold: Double, allowDuplicates: Bool) {
|
|
21
|
+
init(serviceUUIDs: [String], rssiThreshold: Double, allowDuplicates: Bool, androidScanMode: AndroidScanMode) {
|
|
22
22
|
self.init({ () -> bridge.std__vector_std__string_ in
|
|
23
23
|
var __vector = bridge.create_std__vector_std__string_(serviceUUIDs.count)
|
|
24
24
|
for __item in serviceUUIDs {
|
|
25
25
|
__vector.push_back(std.string(__item))
|
|
26
26
|
}
|
|
27
27
|
return __vector
|
|
28
|
-
}(), rssiThreshold, allowDuplicates)
|
|
28
|
+
}(), rssiThreshold, allowDuplicates, androidScanMode)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
var serviceUUIDs: [String] {
|
|
@@ -66,4 +66,15 @@ public extension ScanFilter {
|
|
|
66
66
|
self.__allowDuplicates = newValue
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
var androidScanMode: AndroidScanMode {
|
|
71
|
+
@inline(__always)
|
|
72
|
+
get {
|
|
73
|
+
return self.__androidScanMode
|
|
74
|
+
}
|
|
75
|
+
@inline(__always)
|
|
76
|
+
set {
|
|
77
|
+
self.__androidScanMode = newValue
|
|
78
|
+
}
|
|
79
|
+
}
|
|
69
80
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// AndroidScanMode.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2025 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/JSIConverter.hpp>)
|
|
11
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
#if __has_include(<NitroModules/NitroDefines.hpp>)
|
|
16
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
17
|
+
#else
|
|
18
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
|
+
#endif
|
|
20
|
+
|
|
21
|
+
namespace margelo::nitro::co::zyke::ble {
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An enum which can be represented as a JavaScript enum (AndroidScanMode).
|
|
25
|
+
*/
|
|
26
|
+
enum class AndroidScanMode {
|
|
27
|
+
LOWLATENCY SWIFT_NAME(lowlatency) = 0,
|
|
28
|
+
BALANCED SWIFT_NAME(balanced) = 1,
|
|
29
|
+
LOWPOWER SWIFT_NAME(lowpower) = 2,
|
|
30
|
+
OPPORTUNISTIC SWIFT_NAME(opportunistic) = 3,
|
|
31
|
+
} CLOSED_ENUM;
|
|
32
|
+
|
|
33
|
+
} // namespace margelo::nitro::co::zyke::ble
|
|
34
|
+
|
|
35
|
+
namespace margelo::nitro {
|
|
36
|
+
|
|
37
|
+
// C++ AndroidScanMode <> JS AndroidScanMode (enum)
|
|
38
|
+
template <>
|
|
39
|
+
struct JSIConverter<margelo::nitro::co::zyke::ble::AndroidScanMode> final {
|
|
40
|
+
static inline margelo::nitro::co::zyke::ble::AndroidScanMode fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
41
|
+
int enumValue = JSIConverter<int>::fromJSI(runtime, arg);
|
|
42
|
+
return static_cast<margelo::nitro::co::zyke::ble::AndroidScanMode>(enumValue);
|
|
43
|
+
}
|
|
44
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::co::zyke::ble::AndroidScanMode arg) {
|
|
45
|
+
int enumValue = static_cast<int>(arg);
|
|
46
|
+
return JSIConverter<int>::toJSI(runtime, enumValue);
|
|
47
|
+
}
|
|
48
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
|
|
49
|
+
if (!value.isNumber()) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
double number = value.getNumber();
|
|
53
|
+
int integer = static_cast<int>(number);
|
|
54
|
+
if (number != integer) {
|
|
55
|
+
// The integer is not the same value as the double - we truncated floating points.
|
|
56
|
+
// Enums are all integers, so the input floating point number is obviously invalid.
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// Check if we are within the bounds of the enum.
|
|
60
|
+
return integer >= 0 && integer <= 3;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
} // namespace margelo::nitro
|
|
@@ -26,10 +26,10 @@ namespace margelo::nitro::co::zyke::ble { struct OperationResult; }
|
|
|
26
26
|
|
|
27
27
|
#include "ScanFilter.hpp"
|
|
28
28
|
#include "BLEDevice.hpp"
|
|
29
|
+
#include <optional>
|
|
30
|
+
#include <string>
|
|
29
31
|
#include <functional>
|
|
30
32
|
#include <vector>
|
|
31
|
-
#include <string>
|
|
32
|
-
#include <optional>
|
|
33
33
|
#include <NitroModules/ArrayBuffer.hpp>
|
|
34
34
|
#include "BLEState.hpp"
|
|
35
35
|
#include "OperationResult.hpp"
|
|
@@ -66,7 +66,7 @@ namespace margelo::nitro::co::zyke::ble {
|
|
|
66
66
|
|
|
67
67
|
public:
|
|
68
68
|
// Methods
|
|
69
|
-
virtual void startScan(const ScanFilter& filter, const std::function<void(const BLEDevice
|
|
69
|
+
virtual void startScan(const ScanFilter& filter, const std::function<void(const std::optional<BLEDevice>& /* device */, const std::optional<std::string>& /* error */)>& callback) = 0;
|
|
70
70
|
virtual bool stopScan() = 0;
|
|
71
71
|
virtual bool isScanning() = 0;
|
|
72
72
|
virtual std::vector<BLEDevice> getConnectedDevices(const std::vector<std::string>& services) = 0;
|
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
19
|
#endif
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// Forward declaration of `AndroidScanMode` to properly resolve imports.
|
|
22
|
+
namespace margelo::nitro::co::zyke::ble { enum class AndroidScanMode; }
|
|
22
23
|
|
|
23
24
|
#include <string>
|
|
24
25
|
#include <vector>
|
|
26
|
+
#include "AndroidScanMode.hpp"
|
|
25
27
|
|
|
26
28
|
namespace margelo::nitro::co::zyke::ble {
|
|
27
29
|
|
|
@@ -33,10 +35,11 @@ namespace margelo::nitro::co::zyke::ble {
|
|
|
33
35
|
std::vector<std::string> serviceUUIDs SWIFT_PRIVATE;
|
|
34
36
|
double rssiThreshold SWIFT_PRIVATE;
|
|
35
37
|
bool allowDuplicates SWIFT_PRIVATE;
|
|
38
|
+
AndroidScanMode androidScanMode SWIFT_PRIVATE;
|
|
36
39
|
|
|
37
40
|
public:
|
|
38
41
|
ScanFilter() = default;
|
|
39
|
-
explicit ScanFilter(std::vector<std::string> serviceUUIDs, double rssiThreshold, bool allowDuplicates): serviceUUIDs(serviceUUIDs), rssiThreshold(rssiThreshold), allowDuplicates(allowDuplicates) {}
|
|
42
|
+
explicit ScanFilter(std::vector<std::string> serviceUUIDs, double rssiThreshold, bool allowDuplicates, AndroidScanMode androidScanMode): serviceUUIDs(serviceUUIDs), rssiThreshold(rssiThreshold), allowDuplicates(allowDuplicates), androidScanMode(androidScanMode) {}
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
} // namespace margelo::nitro::co::zyke::ble
|
|
@@ -51,7 +54,8 @@ namespace margelo::nitro {
|
|
|
51
54
|
return margelo::nitro::co::zyke::ble::ScanFilter(
|
|
52
55
|
JSIConverter<std::vector<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "serviceUUIDs")),
|
|
53
56
|
JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, "rssiThreshold")),
|
|
54
|
-
JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, "allowDuplicates"))
|
|
57
|
+
JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, "allowDuplicates")),
|
|
58
|
+
JSIConverter<margelo::nitro::co::zyke::ble::AndroidScanMode>::fromJSI(runtime, obj.getProperty(runtime, "androidScanMode"))
|
|
55
59
|
);
|
|
56
60
|
}
|
|
57
61
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::co::zyke::ble::ScanFilter& arg) {
|
|
@@ -59,6 +63,7 @@ namespace margelo::nitro {
|
|
|
59
63
|
obj.setProperty(runtime, "serviceUUIDs", JSIConverter<std::vector<std::string>>::toJSI(runtime, arg.serviceUUIDs));
|
|
60
64
|
obj.setProperty(runtime, "rssiThreshold", JSIConverter<double>::toJSI(runtime, arg.rssiThreshold));
|
|
61
65
|
obj.setProperty(runtime, "allowDuplicates", JSIConverter<bool>::toJSI(runtime, arg.allowDuplicates));
|
|
66
|
+
obj.setProperty(runtime, "androidScanMode", JSIConverter<margelo::nitro::co::zyke::ble::AndroidScanMode>::toJSI(runtime, arg.androidScanMode));
|
|
62
67
|
return obj;
|
|
63
68
|
}
|
|
64
69
|
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
@@ -69,6 +74,7 @@ namespace margelo::nitro {
|
|
|
69
74
|
if (!JSIConverter<std::vector<std::string>>::canConvert(runtime, obj.getProperty(runtime, "serviceUUIDs"))) return false;
|
|
70
75
|
if (!JSIConverter<double>::canConvert(runtime, obj.getProperty(runtime, "rssiThreshold"))) return false;
|
|
71
76
|
if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, "allowDuplicates"))) return false;
|
|
77
|
+
if (!JSIConverter<margelo::nitro::co::zyke::ble::AndroidScanMode>::canConvert(runtime, obj.getProperty(runtime, "androidScanMode"))) return false;
|
|
72
78
|
return true;
|
|
73
79
|
}
|
|
74
80
|
};
|
package/package.json
CHANGED
package/plugin/build/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ import withBleNitro, { withBleNitroAndroid, withBleNitroIOS, type BleNitroPlugin
|
|
|
15
15
|
* {
|
|
16
16
|
* "isBackgroundEnabled": true,
|
|
17
17
|
* "modes": ["peripheral", "central"],
|
|
18
|
+
* "neverForLocation": true,
|
|
19
|
+
* "androidAdvertisingEnabled": true,
|
|
18
20
|
* "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
|
|
19
21
|
* }
|
|
20
22
|
* ]
|
package/plugin/build/index.js
CHANGED
|
@@ -53,6 +53,8 @@ Object.defineProperty(exports, "withBleNitroIOS", { enumerable: true, get: funct
|
|
|
53
53
|
* {
|
|
54
54
|
* "isBackgroundEnabled": true,
|
|
55
55
|
* "modes": ["peripheral", "central"],
|
|
56
|
+
* "neverForLocation": true,
|
|
57
|
+
* "androidAdvertisingEnabled": true,
|
|
56
58
|
* "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
|
|
57
59
|
* }
|
|
58
60
|
* ]
|
|
@@ -11,7 +11,6 @@ export interface BleNitroPluginProps {
|
|
|
11
11
|
* physical location from Bluetooth scan results. The location permission
|
|
12
12
|
* will still be required on older Android devices.
|
|
13
13
|
* @default false
|
|
14
|
-
* @warning This parameter is experimental and BLE might not work. Test before releasing.
|
|
15
14
|
*/
|
|
16
15
|
neverForLocation?: boolean;
|
|
17
16
|
/**
|
|
@@ -24,6 +23,11 @@ export interface BleNitroPluginProps {
|
|
|
24
23
|
* @default "Allow $(PRODUCT_NAME) to connect to bluetooth devices"
|
|
25
24
|
*/
|
|
26
25
|
bluetoothAlwaysPermission?: string | false;
|
|
26
|
+
/**
|
|
27
|
+
* Android advertising permission message. Set to false to skip adding the permission.
|
|
28
|
+
* @default "Allow $(PRODUCT_NAME) to advertise bluetooth devices"
|
|
29
|
+
*/
|
|
30
|
+
androidAdvertisingEnabled?: boolean;
|
|
27
31
|
}
|
|
28
32
|
export declare const withBleNitroAndroid: ConfigPlugin<BleNitroPluginProps>;
|
|
29
33
|
export declare const withBleNitroIOS: ConfigPlugin<BleNitroPluginProps>;
|
|
@@ -5,22 +5,26 @@ const config_plugins_1 = require("@expo/config-plugins");
|
|
|
5
5
|
const BLUETOOTH_PERMISSIONS = [
|
|
6
6
|
'android.permission.BLUETOOTH',
|
|
7
7
|
'android.permission.BLUETOOTH_ADMIN',
|
|
8
|
-
'android.permission.ACCESS_COARSE_LOCATION',
|
|
8
|
+
// 'android.permission.ACCESS_COARSE_LOCATION',
|
|
9
9
|
'android.permission.ACCESS_FINE_LOCATION',
|
|
10
10
|
];
|
|
11
11
|
const BLUETOOTH_PERMISSIONS_API_31 = [
|
|
12
12
|
'android.permission.BLUETOOTH_SCAN',
|
|
13
|
-
'android.permission.BLUETOOTH_ADVERTISE',
|
|
13
|
+
// 'android.permission.BLUETOOTH_ADVERTISE',
|
|
14
14
|
'android.permission.BLUETOOTH_CONNECT',
|
|
15
15
|
];
|
|
16
|
+
const BLUETOOTH_ADVERTISE_API_31 = [
|
|
17
|
+
'android.permission.BLUETOOTH_ADVERTISE',
|
|
18
|
+
];
|
|
16
19
|
const withBleNitroAndroid = (config, props = {}) => {
|
|
17
|
-
const { isBackgroundEnabled = false, neverForLocation = false } = props;
|
|
20
|
+
const { isBackgroundEnabled = false, neverForLocation = false, androidAdvertisingEnabled = false } = props;
|
|
18
21
|
return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
19
22
|
const androidManifest = config.modResults;
|
|
20
23
|
// Add required permissions
|
|
21
24
|
config_plugins_1.AndroidConfig.Permissions.ensurePermissions(config.modResults, [
|
|
22
25
|
...BLUETOOTH_PERMISSIONS,
|
|
23
26
|
...BLUETOOTH_PERMISSIONS_API_31,
|
|
27
|
+
...(androidAdvertisingEnabled ? BLUETOOTH_ADVERTISE_API_31 : []),
|
|
24
28
|
]);
|
|
25
29
|
// Add uses-feature for BLE
|
|
26
30
|
if (!androidManifest.manifest['uses-feature']) {
|
|
@@ -38,16 +42,23 @@ const withBleNitroAndroid = (config, props = {}) => {
|
|
|
38
42
|
usesFeatures.push(bleFeature);
|
|
39
43
|
}
|
|
40
44
|
// Handle location permission settings for Android 12+
|
|
45
|
+
const permissions = androidManifest.manifest['uses-permission'] || [];
|
|
41
46
|
if (neverForLocation) {
|
|
42
|
-
// Add neverForLocation attribute to location permissions for Android 12+
|
|
43
|
-
const permissions = androidManifest.manifest['uses-permission'] || [];
|
|
44
47
|
permissions.forEach((permission) => {
|
|
45
|
-
if (permission.$?.['android:name'] === 'android.permission.ACCESS_FINE_LOCATION'
|
|
46
|
-
permission
|
|
48
|
+
if (permission.$?.['android:name'] === 'android.permission.ACCESS_FINE_LOCATION') {
|
|
49
|
+
permission.$['android:maxSdkVersion'] = '30';
|
|
50
|
+
}
|
|
51
|
+
if (permission.$?.['android:name'] === 'android.permission.BLUETOOTH_SCAN') {
|
|
47
52
|
permission.$['android:usesPermissionFlags'] = 'neverForLocation';
|
|
48
53
|
}
|
|
49
54
|
});
|
|
50
55
|
}
|
|
56
|
+
permissions.forEach((permission) => {
|
|
57
|
+
if (permission.$?.['android:name'] === 'android.permission.BLUETOOTH' ||
|
|
58
|
+
permission.$?.['android:name'] === 'android.permission.BLUETOOTH_ADMIN') {
|
|
59
|
+
permission.$['android:maxSdkVersion'] = '30';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
51
62
|
return config;
|
|
52
63
|
});
|
|
53
64
|
};
|
|
@@ -8,6 +8,7 @@ jest.mock('../specs/NativeBleNitro', () => ({
|
|
|
8
8
|
connect: jest.fn(),
|
|
9
9
|
disconnect: jest.fn(),
|
|
10
10
|
isConnected: jest.fn(),
|
|
11
|
+
requestMTU: jest.fn(),
|
|
11
12
|
discoverServices: jest.fn(),
|
|
12
13
|
getServices: jest.fn(),
|
|
13
14
|
getCharacteristics: jest.fn(),
|
|
@@ -15,12 +16,27 @@ jest.mock('../specs/NativeBleNitro', () => ({
|
|
|
15
16
|
writeCharacteristic: jest.fn(),
|
|
16
17
|
subscribeToCharacteristic: jest.fn(),
|
|
17
18
|
unsubscribeFromCharacteristic: jest.fn(),
|
|
18
|
-
|
|
19
|
+
getConnectedDevices: jest.fn(),
|
|
19
20
|
requestBluetoothEnable: jest.fn(),
|
|
20
21
|
state: jest.fn(),
|
|
21
22
|
subscribeToStateChange: jest.fn(),
|
|
23
|
+
unsubscribeFromStateChange: jest.fn(),
|
|
24
|
+
openSettings: jest.fn(),
|
|
25
|
+
},
|
|
26
|
+
BLEState: {
|
|
27
|
+
Unknown: 0,
|
|
28
|
+
Resetting: 1,
|
|
29
|
+
Unsupported: 2,
|
|
30
|
+
Unauthorized: 3,
|
|
31
|
+
PoweredOff: 4,
|
|
32
|
+
PoweredOn: 5
|
|
33
|
+
},
|
|
34
|
+
AndroidScanMode: {
|
|
35
|
+
LowLatency: 0,
|
|
36
|
+
Balanced: 1,
|
|
37
|
+
LowPower: 2,
|
|
38
|
+
Opportunistic: 3
|
|
22
39
|
},
|
|
23
|
-
BLEState: { PoweredOn: 5, PoweredOff: 4 },
|
|
24
40
|
}));
|
|
25
41
|
|
|
26
42
|
import { ble as BleNitro } from '../index';
|
|
@@ -46,15 +62,23 @@ describe('BleNitro', () => {
|
|
|
46
62
|
serviceUUIDs: ['test'],
|
|
47
63
|
rssiThreshold: -100,
|
|
48
64
|
allowDuplicates: false,
|
|
65
|
+
androidScanMode: 1, // AndroidScanMode.Balanced (default)
|
|
49
66
|
},
|
|
50
67
|
expect.any(Function)
|
|
51
68
|
);
|
|
52
69
|
});
|
|
53
70
|
|
|
54
|
-
test('stopScan calls native and resolves', () => {
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
test('stopScan calls native and resolves', async () => {
|
|
72
|
+
// First start a scan to set _isScanning to true
|
|
73
|
+
mockNative.startScan.mockImplementation((filter, callback) => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
74
|
+
// Just start scanning
|
|
57
75
|
});
|
|
76
|
+
|
|
77
|
+
const scanCallback = jest.fn();
|
|
78
|
+
await BleNitro.startScan({ serviceUUIDs: ['test'] }, scanCallback);
|
|
79
|
+
|
|
80
|
+
// Now stop the scan
|
|
81
|
+
mockNative.stopScan.mockImplementation(() => true);
|
|
58
82
|
|
|
59
83
|
BleNitro.stopScan();
|
|
60
84
|
|
|
@@ -91,8 +115,9 @@ describe('BleNitro', () => {
|
|
|
91
115
|
});
|
|
92
116
|
|
|
93
117
|
test('writeCharacteristic requires connected device', async () => {
|
|
118
|
+
const data = [1, 2, 3];
|
|
94
119
|
await expect(
|
|
95
|
-
BleNitro.writeCharacteristic('device', 'service', 'char',
|
|
120
|
+
BleNitro.writeCharacteristic('device', 'service', 'char',data)
|
|
96
121
|
).rejects.toThrow('Device not connected');
|
|
97
122
|
});
|
|
98
123
|
|
|
@@ -104,8 +129,9 @@ describe('BleNitro', () => {
|
|
|
104
129
|
await BleNitro.connect('device');
|
|
105
130
|
|
|
106
131
|
// Then read
|
|
107
|
-
mockNative.readCharacteristic.mockImplementation((_device: string, _service: string, _char: string, callback: (success: boolean, data:
|
|
108
|
-
|
|
132
|
+
mockNative.readCharacteristic.mockImplementation((_device: string, _service: string, _char: string, callback: (success: boolean, data: ArrayBuffer, error: string) => void) => {
|
|
133
|
+
const testData = new Uint8Array([85]);
|
|
134
|
+
callback(true, testData.buffer, ''); // Battery level 85%
|
|
109
135
|
});
|
|
110
136
|
|
|
111
137
|
const result = await BleNitro.readCharacteristic('device', 'service', 'char');
|
|
@@ -117,6 +143,9 @@ describe('BleNitro', () => {
|
|
|
117
143
|
'0000char-0000-1000-8000-00805f9b34fb', // 'char' padded to 8 chars
|
|
118
144
|
expect.any(Function)
|
|
119
145
|
);
|
|
146
|
+
|
|
147
|
+
// Result should be number array (ByteArray)
|
|
148
|
+
expect(Array.isArray(result)).toBe(true);
|
|
120
149
|
expect(result).toEqual([85]);
|
|
121
150
|
});
|
|
122
151
|
|
|
@@ -146,17 +175,22 @@ describe('BleNitro', () => {
|
|
|
146
175
|
await BleNitro.connect('device');
|
|
147
176
|
|
|
148
177
|
// Mock subscription
|
|
149
|
-
mockNative.subscribeToCharacteristic.mockImplementation((_device: string, _service: string, _char: string, updateCallback: (charId: string, data:
|
|
178
|
+
mockNative.subscribeToCharacteristic.mockImplementation((_device: string, _service: string, _char: string, updateCallback: (charId: string, data: ArrayBuffer) => void, resultCallback: (success: boolean, error: string) => void) => {
|
|
150
179
|
resultCallback(true, '');
|
|
151
180
|
// Simulate notification
|
|
152
|
-
|
|
181
|
+
const testData = new Uint8Array([1, 2, 3]);
|
|
182
|
+
updateCallback('char-id', testData.buffer);
|
|
153
183
|
});
|
|
154
184
|
|
|
155
185
|
const notificationCallback = jest.fn();
|
|
156
|
-
BleNitro.subscribeToCharacteristic('device', 'service', 'char', notificationCallback);
|
|
186
|
+
const subscription = BleNitro.subscribeToCharacteristic('device', 'service', 'char', notificationCallback);
|
|
157
187
|
|
|
158
188
|
expect(mockNative.subscribeToCharacteristic).toHaveBeenCalled();
|
|
159
189
|
expect(notificationCallback).toHaveBeenCalledWith('char-id', [1, 2, 3]);
|
|
190
|
+
|
|
191
|
+
// Verify subscription object
|
|
192
|
+
expect(subscription).toHaveProperty('remove');
|
|
193
|
+
expect(typeof subscription.remove).toBe('function');
|
|
160
194
|
});
|
|
161
195
|
|
|
162
196
|
test('connect with disconnect event callback', async () => {
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
ScanFilter as NativeScanFilter,
|
|
4
4
|
BLEDevice as NativeBLEDevice,
|
|
5
5
|
BLEState as NativeBLEState,
|
|
6
|
+
ScanCallback as NativeScanCallback,
|
|
7
|
+
AndroidScanMode as NativeAndroidScanMode,
|
|
6
8
|
} from './specs/NativeBleNitro';
|
|
7
9
|
|
|
8
10
|
export type ByteArray = number[];
|
|
@@ -11,6 +13,7 @@ export interface ScanFilter {
|
|
|
11
13
|
serviceUUIDs?: string[];
|
|
12
14
|
rssiThreshold?: number;
|
|
13
15
|
allowDuplicates?: boolean;
|
|
16
|
+
androidScanMode?: AndroidScanMode;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export interface ManufacturerDataEntry {
|
|
@@ -32,6 +35,7 @@ export interface BLEDevice {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export type ScanCallback = (device: BLEDevice) => void;
|
|
38
|
+
export type RestoreStateCallback = (connectedPeripherals: BLEDevice[]) => void;
|
|
35
39
|
export type ConnectionCallback = (
|
|
36
40
|
success: boolean,
|
|
37
41
|
deviceId: string,
|
|
@@ -61,6 +65,18 @@ export enum BLEState {
|
|
|
61
65
|
PoweredOn = 'PoweredOn',
|
|
62
66
|
};
|
|
63
67
|
|
|
68
|
+
export enum AndroidScanMode {
|
|
69
|
+
LowLatency = 'LowLatency',
|
|
70
|
+
Balanced = 'Balanced',
|
|
71
|
+
LowPower = 'LowPower',
|
|
72
|
+
Opportunistic = 'Opportunistic',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type BleNitroOptions = {
|
|
76
|
+
restoreStateIdentifier?: string;
|
|
77
|
+
onRestoreState?: RestoreStateCallback;
|
|
78
|
+
};
|
|
79
|
+
|
|
64
80
|
function mapNativeBLEStateToBLEState(nativeState: NativeBLEState): BLEState {
|
|
65
81
|
const map = {
|
|
66
82
|
0: BLEState.Unknown,
|
|
@@ -73,6 +89,29 @@ function mapNativeBLEStateToBLEState(nativeState: NativeBLEState): BLEState {
|
|
|
73
89
|
return map[nativeState];
|
|
74
90
|
}
|
|
75
91
|
|
|
92
|
+
function mapAndroidScanModeToNativeAndroidScanMode(scanMode: AndroidScanMode): NativeAndroidScanMode {
|
|
93
|
+
const map = {
|
|
94
|
+
LowLatency: NativeAndroidScanMode.LowLatency,
|
|
95
|
+
Balanced: NativeAndroidScanMode.Balanced,
|
|
96
|
+
LowPower: NativeAndroidScanMode.LowPower,
|
|
97
|
+
Opportunistic: NativeAndroidScanMode.Opportunistic,
|
|
98
|
+
}
|
|
99
|
+
return map[scanMode];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function convertNativeBleDeviceToBleDevice(nativeBleDevice: NativeBLEDevice): BLEDevice {
|
|
103
|
+
return {
|
|
104
|
+
...nativeBleDevice,
|
|
105
|
+
serviceUUIDs: BleNitro.normalizeGattUUIDs(nativeBleDevice.serviceUUIDs),
|
|
106
|
+
manufacturerData: {
|
|
107
|
+
companyIdentifiers: nativeBleDevice.manufacturerData.companyIdentifiers.map(entry => ({
|
|
108
|
+
id: entry.id,
|
|
109
|
+
data: arrayBufferToByteArray(entry.data)
|
|
110
|
+
}))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
76
115
|
function arrayBufferToByteArray(buffer: ArrayBuffer): ByteArray {
|
|
77
116
|
return Array.from(new Uint8Array(buffer));
|
|
78
117
|
}
|
|
@@ -126,7 +165,8 @@ export class BleNitro {
|
|
|
126
165
|
*/
|
|
127
166
|
public startScan(
|
|
128
167
|
filter: ScanFilter = {},
|
|
129
|
-
callback: ScanCallback
|
|
168
|
+
callback: ScanCallback,
|
|
169
|
+
onError?: (error: string) => void,
|
|
130
170
|
): void {
|
|
131
171
|
if (this._isScanning) {
|
|
132
172
|
return;
|
|
@@ -137,21 +177,19 @@ export class BleNitro {
|
|
|
137
177
|
serviceUUIDs: filter.serviceUUIDs || [],
|
|
138
178
|
rssiThreshold: filter.rssiThreshold ?? -100,
|
|
139
179
|
allowDuplicates: filter.allowDuplicates ?? false,
|
|
180
|
+
androidScanMode: mapAndroidScanModeToNativeAndroidScanMode(filter.androidScanMode ?? AndroidScanMode.Balanced),
|
|
140
181
|
};
|
|
141
182
|
|
|
142
183
|
// Create callback wrapper
|
|
143
|
-
const scanCallback = (device: NativeBLEDevice) => {
|
|
184
|
+
const scanCallback: NativeScanCallback = (device: NativeBLEDevice | null, error: string | null) => {
|
|
185
|
+
if (error && !device) {
|
|
186
|
+
this._isScanning = false;
|
|
187
|
+
onError?.(error);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
device = device!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
144
191
|
// Convert manufacturer data to Uint8Arrays
|
|
145
|
-
const convertedDevice: BLEDevice =
|
|
146
|
-
...device,
|
|
147
|
-
serviceUUIDs: BleNitro.normalizeGattUUIDs(device.serviceUUIDs),
|
|
148
|
-
manufacturerData: {
|
|
149
|
-
companyIdentifiers: device.manufacturerData.companyIdentifiers.map(entry => ({
|
|
150
|
-
id: entry.id,
|
|
151
|
-
data: arrayBufferToByteArray(entry.data)
|
|
152
|
-
}))
|
|
153
|
-
}
|
|
154
|
-
};
|
|
192
|
+
const convertedDevice: BLEDevice = convertNativeBleDeviceToBleDevice(device);
|
|
155
193
|
callback(convertedDevice);
|
|
156
194
|
};
|
|
157
195
|
|
|
@@ -170,6 +208,7 @@ export class BleNitro {
|
|
|
170
208
|
}
|
|
171
209
|
|
|
172
210
|
BleNitroNative.stopScan();
|
|
211
|
+
this._isScanning = false;
|
|
173
212
|
}
|
|
174
213
|
|
|
175
214
|
/**
|
|
@@ -189,16 +228,7 @@ export class BleNitro {
|
|
|
189
228
|
public getConnectedDevices(services?: string[]): BLEDevice[] {
|
|
190
229
|
const devices = BleNitroNative.getConnectedDevices(services || []);
|
|
191
230
|
// Normalize service UUIDs - manufacturer data already comes as ArrayBuffers
|
|
192
|
-
return devices.map(device => (
|
|
193
|
-
...device,
|
|
194
|
-
serviceUUIDs: BleNitro.normalizeGattUUIDs(device.serviceUUIDs),
|
|
195
|
-
manufacturerData: {
|
|
196
|
-
companyIdentifiers: device.manufacturerData.companyIdentifiers.map(entry => ({
|
|
197
|
-
id: entry.id,
|
|
198
|
-
data: arrayBufferToByteArray(entry.data)
|
|
199
|
-
}))
|
|
200
|
-
}
|
|
201
|
-
}));
|
|
231
|
+
return devices.map(device => convertNativeBleDeviceToBleDevice(device));
|
|
202
232
|
}
|
|
203
233
|
|
|
204
234
|
/**
|
|
@@ -360,7 +390,7 @@ export class BleNitro {
|
|
|
360
390
|
* @param deviceId ID of the device
|
|
361
391
|
* @param serviceId ID of the service
|
|
362
392
|
* @param characteristicId ID of the characteristic
|
|
363
|
-
* @returns Promise resolving to the characteristic data as
|
|
393
|
+
* @returns Promise resolving to the characteristic data as ArrayBuffer
|
|
364
394
|
*/
|
|
365
395
|
public readCharacteristic(
|
|
366
396
|
deviceId: string,
|
|
@@ -394,7 +424,7 @@ export class BleNitro {
|
|
|
394
424
|
* @param deviceId ID of the device
|
|
395
425
|
* @param serviceId ID of the service
|
|
396
426
|
* @param characteristicId ID of the characteristic
|
|
397
|
-
* @param data Data to write as ByteArray(number[])
|
|
427
|
+
* @param data Data to write as ByteArray (number[])
|
|
398
428
|
* @param withResponse Whether to wait for response
|
|
399
429
|
* @returns Promise resolving when write is complete
|
|
400
430
|
*/
|
|
@@ -412,13 +442,11 @@ export class BleNitro {
|
|
|
412
442
|
return;
|
|
413
443
|
}
|
|
414
444
|
|
|
415
|
-
const dataAsArrayBuffer = byteArrayToArrayBuffer(data);
|
|
416
|
-
|
|
417
445
|
BleNitroNative.writeCharacteristic(
|
|
418
446
|
deviceId,
|
|
419
447
|
BleNitro.normalizeGattUUID(serviceId),
|
|
420
448
|
BleNitro.normalizeGattUUID(characteristicId),
|
|
421
|
-
|
|
449
|
+
byteArrayToArrayBuffer(data),
|
|
422
450
|
withResponse,
|
|
423
451
|
(success: boolean, error: string) => {
|
|
424
452
|
if (success) {
|
|
@@ -31,13 +31,21 @@ export interface BLEDevice {
|
|
|
31
31
|
isConnectable: boolean;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export enum AndroidScanMode {
|
|
35
|
+
LowLatency = 0,
|
|
36
|
+
Balanced = 1,
|
|
37
|
+
LowPower = 2,
|
|
38
|
+
Opportunistic = 3,
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
export interface ScanFilter {
|
|
35
42
|
serviceUUIDs: string[];
|
|
36
43
|
rssiThreshold: number;
|
|
37
44
|
allowDuplicates: boolean;
|
|
45
|
+
androidScanMode: AndroidScanMode;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
export type ScanCallback = (device: BLEDevice) => void;
|
|
48
|
+
export type ScanCallback = (device: BLEDevice | null, error: string | null) => void;
|
|
41
49
|
export type DevicesCallback = (devices: BLEDevice[]) => void;
|
|
42
50
|
export type ConnectionCallback = (success: boolean, deviceId: string, error: string) => void;
|
|
43
51
|
export type DisconnectionEventCallback = (deviceId: string, interrupted: boolean, error: string) => void;
|