react-native-ble-nitro 1.2.0 → 1.3.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/README.md +81 -18
- 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 +14 -8
- package/lib/commonjs/index.d.ts.map +1 -1
- package/lib/commonjs/index.js +35 -17
- 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 +14 -8
- package/lib/index.js +33 -15
- 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 +48 -13
- package/src/index.ts +40 -21
- 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 = new Uint8Array([1, 2, 3]);
|
|
94
119
|
await expect(
|
|
95
|
-
BleNitro.writeCharacteristic('device', 'service', 'char',
|
|
120
|
+
BleNitro.writeCharacteristic('device', 'service', 'char', data.buffer)
|
|
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,7 +143,11 @@ describe('BleNitro', () => {
|
|
|
117
143
|
'0000char-0000-1000-8000-00805f9b34fb', // 'char' padded to 8 chars
|
|
118
144
|
expect.any(Function)
|
|
119
145
|
);
|
|
120
|
-
|
|
146
|
+
|
|
147
|
+
// Result should be ArrayBuffer
|
|
148
|
+
expect(result).toBeInstanceOf(ArrayBuffer);
|
|
149
|
+
const resultArray = new Uint8Array(result);
|
|
150
|
+
expect(resultArray[0]).toBe(85);
|
|
121
151
|
});
|
|
122
152
|
|
|
123
153
|
test('disconnect calls native', async () => {
|
|
@@ -146,17 +176,22 @@ describe('BleNitro', () => {
|
|
|
146
176
|
await BleNitro.connect('device');
|
|
147
177
|
|
|
148
178
|
// Mock subscription
|
|
149
|
-
mockNative.subscribeToCharacteristic.mockImplementation((_device: string, _service: string, _char: string, updateCallback: (charId: string, data:
|
|
179
|
+
mockNative.subscribeToCharacteristic.mockImplementation((_device: string, _service: string, _char: string, updateCallback: (charId: string, data: ArrayBuffer) => void, resultCallback: (success: boolean, error: string) => void) => {
|
|
150
180
|
resultCallback(true, '');
|
|
151
181
|
// Simulate notification
|
|
152
|
-
|
|
182
|
+
const testData = new Uint8Array([1, 2, 3]);
|
|
183
|
+
updateCallback('char-id', testData.buffer);
|
|
153
184
|
});
|
|
154
185
|
|
|
155
186
|
const notificationCallback = jest.fn();
|
|
156
|
-
BleNitro.subscribeToCharacteristic('device', 'service', 'char', notificationCallback);
|
|
187
|
+
const subscription = BleNitro.subscribeToCharacteristic('device', 'service', 'char', notificationCallback);
|
|
157
188
|
|
|
158
189
|
expect(mockNative.subscribeToCharacteristic).toHaveBeenCalled();
|
|
159
|
-
expect(notificationCallback).toHaveBeenCalledWith('char-id',
|
|
190
|
+
expect(notificationCallback).toHaveBeenCalledWith('char-id', expect.any(ArrayBuffer));
|
|
191
|
+
|
|
192
|
+
// Verify subscription object
|
|
193
|
+
expect(subscription).toHaveProperty('remove');
|
|
194
|
+
expect(typeof subscription.remove).toBe('function');
|
|
160
195
|
});
|
|
161
196
|
|
|
162
197
|
test('connect with disconnect event callback', async () => {
|
package/src/index.ts
CHANGED
|
@@ -3,19 +3,21 @@ 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
|
-
export type ByteArray = number[];
|
|
9
10
|
|
|
10
11
|
export interface ScanFilter {
|
|
11
12
|
serviceUUIDs?: string[];
|
|
12
13
|
rssiThreshold?: number;
|
|
13
14
|
allowDuplicates?: boolean;
|
|
15
|
+
androidScanMode?: AndroidScanMode;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface ManufacturerDataEntry {
|
|
17
19
|
id: string;
|
|
18
|
-
data:
|
|
20
|
+
data: ArrayBuffer;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export interface ManufacturerData {
|
|
@@ -45,7 +47,7 @@ export type DisconnectEventCallback = (
|
|
|
45
47
|
export type OperationCallback = (success: boolean, error: string) => void;
|
|
46
48
|
export type CharacteristicUpdateCallback = (
|
|
47
49
|
characteristicId: string,
|
|
48
|
-
data:
|
|
50
|
+
data: ArrayBuffer
|
|
49
51
|
) => void;
|
|
50
52
|
|
|
51
53
|
export type Subscription = {
|
|
@@ -61,6 +63,13 @@ export enum BLEState {
|
|
|
61
63
|
PoweredOn = 'PoweredOn',
|
|
62
64
|
};
|
|
63
65
|
|
|
66
|
+
export enum AndroidScanMode {
|
|
67
|
+
LowLatency = 'LowLatency',
|
|
68
|
+
Balanced = 'Balanced',
|
|
69
|
+
LowPower = 'LowPower',
|
|
70
|
+
Opportunistic = 'Opportunistic',
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
function mapNativeBLEStateToBLEState(nativeState: NativeBLEState): BLEState {
|
|
65
74
|
const map = {
|
|
66
75
|
0: BLEState.Unknown,
|
|
@@ -73,13 +82,16 @@ function mapNativeBLEStateToBLEState(nativeState: NativeBLEState): BLEState {
|
|
|
73
82
|
return map[nativeState];
|
|
74
83
|
}
|
|
75
84
|
|
|
76
|
-
function
|
|
77
|
-
|
|
85
|
+
function mapAndroidScanModeToNativeAndroidScanMode(scanMode: AndroidScanMode): NativeAndroidScanMode {
|
|
86
|
+
const map = {
|
|
87
|
+
LowLatency: NativeAndroidScanMode.LowLatency,
|
|
88
|
+
Balanced: NativeAndroidScanMode.Balanced,
|
|
89
|
+
LowPower: NativeAndroidScanMode.LowPower,
|
|
90
|
+
Opportunistic: NativeAndroidScanMode.Opportunistic,
|
|
91
|
+
}
|
|
92
|
+
return map[scanMode];
|
|
78
93
|
}
|
|
79
94
|
|
|
80
|
-
function byteArrayToArrayBuffer(data: ByteArray): ArrayBuffer {
|
|
81
|
-
return new Uint8Array(data).buffer;
|
|
82
|
-
}
|
|
83
95
|
|
|
84
96
|
let _instance: BleNitro;
|
|
85
97
|
|
|
@@ -126,7 +138,8 @@ export class BleNitro {
|
|
|
126
138
|
*/
|
|
127
139
|
public startScan(
|
|
128
140
|
filter: ScanFilter = {},
|
|
129
|
-
callback: ScanCallback
|
|
141
|
+
callback: ScanCallback,
|
|
142
|
+
onError?: (error: string) => void,
|
|
130
143
|
): void {
|
|
131
144
|
if (this._isScanning) {
|
|
132
145
|
return;
|
|
@@ -137,10 +150,17 @@ export class BleNitro {
|
|
|
137
150
|
serviceUUIDs: filter.serviceUUIDs || [],
|
|
138
151
|
rssiThreshold: filter.rssiThreshold ?? -100,
|
|
139
152
|
allowDuplicates: filter.allowDuplicates ?? false,
|
|
153
|
+
androidScanMode: mapAndroidScanModeToNativeAndroidScanMode(filter.androidScanMode ?? AndroidScanMode.Balanced),
|
|
140
154
|
};
|
|
141
155
|
|
|
142
156
|
// Create callback wrapper
|
|
143
|
-
const scanCallback = (device: NativeBLEDevice) => {
|
|
157
|
+
const scanCallback: NativeScanCallback = (device: NativeBLEDevice | null, error: string | null) => {
|
|
158
|
+
if (error && !device) {
|
|
159
|
+
this._isScanning = false;
|
|
160
|
+
onError?.(error);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
device = device!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
|
144
164
|
// Convert manufacturer data to Uint8Arrays
|
|
145
165
|
const convertedDevice: BLEDevice = {
|
|
146
166
|
...device,
|
|
@@ -148,7 +168,7 @@ export class BleNitro {
|
|
|
148
168
|
manufacturerData: {
|
|
149
169
|
companyIdentifiers: device.manufacturerData.companyIdentifiers.map(entry => ({
|
|
150
170
|
id: entry.id,
|
|
151
|
-
data:
|
|
171
|
+
data: entry.data
|
|
152
172
|
}))
|
|
153
173
|
}
|
|
154
174
|
};
|
|
@@ -170,6 +190,7 @@ export class BleNitro {
|
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
BleNitroNative.stopScan();
|
|
193
|
+
this._isScanning = false;
|
|
173
194
|
}
|
|
174
195
|
|
|
175
196
|
/**
|
|
@@ -195,7 +216,7 @@ export class BleNitro {
|
|
|
195
216
|
manufacturerData: {
|
|
196
217
|
companyIdentifiers: device.manufacturerData.companyIdentifiers.map(entry => ({
|
|
197
218
|
id: entry.id,
|
|
198
|
-
data:
|
|
219
|
+
data: entry.data
|
|
199
220
|
}))
|
|
200
221
|
}
|
|
201
222
|
}));
|
|
@@ -360,13 +381,13 @@ export class BleNitro {
|
|
|
360
381
|
* @param deviceId ID of the device
|
|
361
382
|
* @param serviceId ID of the service
|
|
362
383
|
* @param characteristicId ID of the characteristic
|
|
363
|
-
* @returns Promise resolving to the characteristic data as
|
|
384
|
+
* @returns Promise resolving to the characteristic data as ArrayBuffer
|
|
364
385
|
*/
|
|
365
386
|
public readCharacteristic(
|
|
366
387
|
deviceId: string,
|
|
367
388
|
serviceId: string,
|
|
368
389
|
characteristicId: string
|
|
369
|
-
): Promise<
|
|
390
|
+
): Promise<ArrayBuffer> {
|
|
370
391
|
return new Promise((resolve, reject) => {
|
|
371
392
|
// Check if connected first
|
|
372
393
|
if (!this._connectedDevices[deviceId]) {
|
|
@@ -380,7 +401,7 @@ export class BleNitro {
|
|
|
380
401
|
BleNitro.normalizeGattUUID(characteristicId),
|
|
381
402
|
(success: boolean, data: ArrayBuffer, error: string) => {
|
|
382
403
|
if (success) {
|
|
383
|
-
resolve(
|
|
404
|
+
resolve(data);
|
|
384
405
|
} else {
|
|
385
406
|
reject(new Error(error));
|
|
386
407
|
}
|
|
@@ -394,7 +415,7 @@ export class BleNitro {
|
|
|
394
415
|
* @param deviceId ID of the device
|
|
395
416
|
* @param serviceId ID of the service
|
|
396
417
|
* @param characteristicId ID of the characteristic
|
|
397
|
-
* @param data Data to write as
|
|
418
|
+
* @param data Data to write as ArrayBuffer
|
|
398
419
|
* @param withResponse Whether to wait for response
|
|
399
420
|
* @returns Promise resolving when write is complete
|
|
400
421
|
*/
|
|
@@ -402,7 +423,7 @@ export class BleNitro {
|
|
|
402
423
|
deviceId: string,
|
|
403
424
|
serviceId: string,
|
|
404
425
|
characteristicId: string,
|
|
405
|
-
data:
|
|
426
|
+
data: ArrayBuffer,
|
|
406
427
|
withResponse: boolean = true
|
|
407
428
|
): Promise<boolean> {
|
|
408
429
|
return new Promise((resolve, reject) => {
|
|
@@ -412,13 +433,11 @@ export class BleNitro {
|
|
|
412
433
|
return;
|
|
413
434
|
}
|
|
414
435
|
|
|
415
|
-
const dataAsArrayBuffer = byteArrayToArrayBuffer(data);
|
|
416
|
-
|
|
417
436
|
BleNitroNative.writeCharacteristic(
|
|
418
437
|
deviceId,
|
|
419
438
|
BleNitro.normalizeGattUUID(serviceId),
|
|
420
439
|
BleNitro.normalizeGattUUID(characteristicId),
|
|
421
|
-
|
|
440
|
+
data,
|
|
422
441
|
withResponse,
|
|
423
442
|
(success: boolean, error: string) => {
|
|
424
443
|
if (success) {
|
|
@@ -457,7 +476,7 @@ export class BleNitro {
|
|
|
457
476
|
BleNitro.normalizeGattUUID(serviceId),
|
|
458
477
|
BleNitro.normalizeGattUUID(characteristicId),
|
|
459
478
|
(charId: string, data: ArrayBuffer) => {
|
|
460
|
-
callback(charId,
|
|
479
|
+
callback(charId, data);
|
|
461
480
|
},
|
|
462
481
|
(success, error) => {
|
|
463
482
|
_success = 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;
|