react-native-ble-nitro 1.0.0-alpha.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/LICENSE +21 -0
- package/README.md +298 -0
- package/android/build.gradle +55 -0
- package/android/src/main/AndroidManifest.xml +23 -0
- package/android/src/main/kotlin/co/zyke/ble/BleNitroBleManager.kt +651 -0
- package/android/src/main/kotlin/co/zyke/ble/BleNitroPackage.kt +37 -0
- package/ios/BleNitro.podspec +37 -0
- package/ios/BleNitroBleManager.swift +509 -0
- package/ios/BleNitroModule.swift +31 -0
- package/lib/BleManagerCompatFactory.d.ts +53 -0
- package/lib/BleManagerCompatFactory.js +191 -0
- package/lib/BleManagerFactory.d.ts +12 -0
- package/lib/BleManagerFactory.js +22 -0
- package/lib/compatibility/constants.d.ts +49 -0
- package/lib/compatibility/constants.js +50 -0
- package/lib/compatibility/deviceWrapper.d.ts +99 -0
- package/lib/compatibility/deviceWrapper.js +259 -0
- package/lib/compatibility/enums.d.ts +43 -0
- package/lib/compatibility/enums.js +124 -0
- package/lib/compatibility/index.d.ts +11 -0
- package/lib/compatibility/index.js +12 -0
- package/lib/compatibility/serviceData.d.ts +51 -0
- package/lib/compatibility/serviceData.js +70 -0
- package/lib/errors/BleError.d.ts +59 -0
- package/lib/errors/BleError.js +120 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +12 -0
- package/lib/specs/BleManager.nitro.d.ts +36 -0
- package/lib/specs/BleManager.nitro.js +1 -0
- package/lib/specs/Characteristic.nitro.d.ts +26 -0
- package/lib/specs/Characteristic.nitro.js +1 -0
- package/lib/specs/Descriptor.nitro.d.ts +17 -0
- package/lib/specs/Descriptor.nitro.js +1 -0
- package/lib/specs/Device.nitro.d.ts +37 -0
- package/lib/specs/Device.nitro.js +1 -0
- package/lib/specs/Service.nitro.d.ts +19 -0
- package/lib/specs/Service.nitro.js +1 -0
- package/lib/specs/types.d.ts +228 -0
- package/lib/specs/types.js +146 -0
- package/lib/utils/base64.d.ts +25 -0
- package/lib/utils/base64.js +80 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +2 -0
- package/lib/utils/uuid.d.ts +9 -0
- package/lib/utils/uuid.js +37 -0
- package/nitro.json +15 -0
- package/package.json +102 -0
- package/plugin/build/index.d.ts +28 -0
- package/plugin/build/index.js +29 -0
- package/plugin/build/withBleNitro.d.ts +31 -0
- package/plugin/build/withBleNitro.js +87 -0
- package/react-native.config.js +13 -0
- package/src/BleManagerCompatFactory.ts +373 -0
- package/src/BleManagerFactory.ts +30 -0
- package/src/__tests__/BleManager.test.ts +327 -0
- package/src/__tests__/compatibility/deviceWrapper.test.ts +563 -0
- package/src/__tests__/compatibility/enums.test.ts +254 -0
- package/src/compatibility/constants.ts +71 -0
- package/src/compatibility/deviceWrapper.ts +427 -0
- package/src/compatibility/enums.ts +160 -0
- package/src/compatibility/index.ts +24 -0
- package/src/compatibility/serviceData.ts +85 -0
- package/src/errors/BleError.ts +193 -0
- package/src/index.ts +30 -0
- package/src/specs/BleManager.nitro.ts +152 -0
- package/src/specs/Characteristic.nitro.ts +61 -0
- package/src/specs/Descriptor.nitro.ts +28 -0
- package/src/specs/Device.nitro.ts +104 -0
- package/src/specs/Service.nitro.ts +64 -0
- package/src/specs/types.ts +259 -0
- package/src/utils/base64.ts +80 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/uuid.ts +45 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "BleNitro"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => "11.0" }
|
|
14
|
+
s.source = { :git => "https://github.com/zykeco/react-native-ble-nitro.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "**/*.{h,m,mm,swift}"
|
|
17
|
+
s.swift_version = "5.0"
|
|
18
|
+
|
|
19
|
+
# Nitro dependencies
|
|
20
|
+
s.dependency "React-Core"
|
|
21
|
+
s.dependency "NitroModules", "0.26.4"
|
|
22
|
+
|
|
23
|
+
# iOS frameworks
|
|
24
|
+
s.frameworks = "CoreBluetooth"
|
|
25
|
+
|
|
26
|
+
# Build settings
|
|
27
|
+
s.pod_target_xcconfig = {
|
|
28
|
+
'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) FOLLY_NO_CONFIG FOLLY_MOBILE=1 FOLLY_USE_LIBCPP=1',
|
|
29
|
+
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
|
|
30
|
+
'CLANG_CXX_LIBRARY' => 'libc++',
|
|
31
|
+
'OTHER_CPLUSPLUSFLAGS' => '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
s.user_target_xcconfig = {
|
|
35
|
+
'OTHER_LDFLAGS' => '-lc++'
|
|
36
|
+
}
|
|
37
|
+
end
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// BleNitroBleManager.swift
|
|
3
|
+
/// React Native BLE Nitro - iOS Implementation
|
|
4
|
+
/// Copyright © 2025 Zyke (https://zyke.co)
|
|
5
|
+
///
|
|
6
|
+
|
|
7
|
+
import Foundation
|
|
8
|
+
import CoreBluetooth
|
|
9
|
+
import NitroModules
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Core BLE Manager implementation using CoreBluetooth
|
|
13
|
+
* Implements the HybridBleManagerSpec protocol generated by Nitro
|
|
14
|
+
*/
|
|
15
|
+
public class BleNitroBleManager: HybridBleManagerSpec, CBCentralManagerDelegate {
|
|
16
|
+
|
|
17
|
+
// MARK: - Properties
|
|
18
|
+
private var centralManager: CBCentralManager!
|
|
19
|
+
private var logLevel: LogLevel = .None
|
|
20
|
+
private var isScanning = false
|
|
21
|
+
private var scanListener: ((_ error: NativeBleError?, _ scannedDevice: NativeDevice?) -> Void)?
|
|
22
|
+
private var stateChangeListener: ((_ newState: State) -> Void)?
|
|
23
|
+
private var connectedDevices: [String: CBPeripheral] = [:]
|
|
24
|
+
private var discoveredDevices: [String: CBPeripheral] = [:]
|
|
25
|
+
private var deviceDisconnectListeners: [String: ((_ error: NativeBleError?, _ device: NativeDevice?) -> Void)] = [:]
|
|
26
|
+
private var characteristicMonitors: [String: ((_ error: NativeBleError?, _ characteristic: NativeCharacteristic?) -> Void)] = [:]
|
|
27
|
+
private var pendingOperations: [String: Any] = [:]
|
|
28
|
+
|
|
29
|
+
// MARK: - Initialization
|
|
30
|
+
public override init() {
|
|
31
|
+
super.init()
|
|
32
|
+
self.centralManager = CBCentralManager(delegate: self, queue: nil)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public override var memorySize: Int {
|
|
36
|
+
return MemorySize.MemorySize_estimate(self)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// MARK: - HybridBleManagerSpec Implementation
|
|
40
|
+
|
|
41
|
+
public func destroy() throws -> Promise<Void> {
|
|
42
|
+
return Promise.async { resolve, reject in
|
|
43
|
+
self.stopDeviceScan()
|
|
44
|
+
self.centralManager.delegate = nil
|
|
45
|
+
self.connectedDevices.removeAll()
|
|
46
|
+
self.discoveredDevices.removeAll()
|
|
47
|
+
self.deviceDisconnectListeners.removeAll()
|
|
48
|
+
self.characteristicMonitors.removeAll()
|
|
49
|
+
self.pendingOperations.removeAll()
|
|
50
|
+
resolve(())
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public func setLogLevel(logLevel: LogLevel) throws -> Promise<LogLevel> {
|
|
55
|
+
return Promise.resolve(withBlock: {
|
|
56
|
+
self.logLevel = logLevel
|
|
57
|
+
return logLevel
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public func logLevel() throws -> Promise<LogLevel> {
|
|
62
|
+
return Promise.resolve(self.logLevel)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public func cancelTransaction(transactionId: String) throws -> Promise<Void> {
|
|
66
|
+
return Promise.async { resolve, reject in
|
|
67
|
+
self.pendingOperations.removeValue(forKey: transactionId)
|
|
68
|
+
resolve(())
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public func enable(transactionId: String?) throws -> Promise<Void> {
|
|
73
|
+
return Promise.async { resolve, reject in
|
|
74
|
+
// CoreBluetooth manages Bluetooth state automatically
|
|
75
|
+
// We can't programmatically enable Bluetooth on iOS
|
|
76
|
+
if self.centralManager.state == .poweredOn {
|
|
77
|
+
resolve(())
|
|
78
|
+
} else {
|
|
79
|
+
let error = self.createBleError(.BluetoothPoweredOff, message: "Bluetooth is not powered on")
|
|
80
|
+
reject(error)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public func disable(transactionId: String?) throws -> Promise<Void> {
|
|
86
|
+
return Promise.async { resolve, reject in
|
|
87
|
+
// CoreBluetooth doesn't allow programmatic disabling on iOS
|
|
88
|
+
let error = self.createBleError(.BluetoothUnsupported, message: "Cannot programmatically disable Bluetooth on iOS")
|
|
89
|
+
reject(error)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public func state() throws -> Promise<State> {
|
|
94
|
+
return Promise.resolve(self.mapCBManagerState(self.centralManager.state))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public func onStateChange(listener: @escaping (_ newState: State) -> Void, emitCurrentState: Bool?) throws -> Subscription {
|
|
98
|
+
self.stateChangeListener = listener
|
|
99
|
+
|
|
100
|
+
if emitCurrentState == true {
|
|
101
|
+
listener(self.mapCBManagerState(self.centralManager.state))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return SubscriptionImpl {
|
|
105
|
+
self.stateChangeListener = nil
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public func startDeviceScan(uuids: [String]?, options: ScanOptions?, listener: @escaping (_ error: NativeBleError?, _ scannedDevice: NativeDevice?) -> Void) throws -> Promise<Void> {
|
|
110
|
+
return Promise.async { resolve, reject in
|
|
111
|
+
guard self.centralManager.state == .poweredOn else {
|
|
112
|
+
let error = self.createBleError(.BluetoothPoweredOff, message: "Bluetooth is not powered on")
|
|
113
|
+
reject(error)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
self.scanListener = listener
|
|
118
|
+
self.isScanning = true
|
|
119
|
+
|
|
120
|
+
var serviceUUIDs: [CBUUID]? = nil
|
|
121
|
+
if let uuids = uuids {
|
|
122
|
+
serviceUUIDs = uuids.compactMap { CBUUID(string: $0) }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
var scanOptions: [String: Any] = [:]
|
|
126
|
+
if let options = options {
|
|
127
|
+
if options.allowDuplicates == true {
|
|
128
|
+
scanOptions[CBCentralManagerScanOptionAllowDuplicatesKey] = true
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
self.centralManager.scanForPeripherals(withServices: serviceUUIDs, options: scanOptions)
|
|
133
|
+
resolve(())
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public func stopDeviceScan() throws -> Promise<Void> {
|
|
138
|
+
return Promise.resolve(withBlock: {
|
|
139
|
+
if self.isScanning {
|
|
140
|
+
self.centralManager.stopScan()
|
|
141
|
+
self.isScanning = false
|
|
142
|
+
self.scanListener = nil
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public func requestConnectionPriorityForDevice(deviceIdentifier: String, connectionPriority: ConnectionPriority, transactionId: String?) throws -> Promise<NativeDevice> {
|
|
148
|
+
return Promise.async { resolve, reject in
|
|
149
|
+
// iOS doesn't have direct connection priority control like Android
|
|
150
|
+
// Return the device as-is since this is mostly an Android feature
|
|
151
|
+
if let peripheral = self.connectedDevices[deviceIdentifier] {
|
|
152
|
+
let device = self.createNativeDevice(from: peripheral)
|
|
153
|
+
resolve(device)
|
|
154
|
+
} else {
|
|
155
|
+
let error = self.createBleError(.DeviceNotFound, message: "Device not found: \(deviceIdentifier)")
|
|
156
|
+
reject(error)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public func readRSSIForDevice(deviceIdentifier: String, transactionId: String?) throws -> Promise<NativeDevice> {
|
|
162
|
+
return Promise.async { resolve, reject in
|
|
163
|
+
guard let peripheral = self.connectedDevices[deviceIdentifier] else {
|
|
164
|
+
let error = self.createBleError(.DeviceNotFound, message: "Device not found: \(deviceIdentifier)")
|
|
165
|
+
reject(error)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if let transactionId = transactionId {
|
|
170
|
+
self.pendingOperations[transactionId] = resolve
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
peripheral.readRSSI()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public func requestMTUForDevice(deviceIdentifier: String, mtu: Double, transactionId: String?) throws -> Promise<NativeDevice> {
|
|
178
|
+
return Promise.async { resolve, reject in
|
|
179
|
+
// iOS automatically negotiates MTU, we can't explicitly set it
|
|
180
|
+
if let peripheral = self.connectedDevices[deviceIdentifier] {
|
|
181
|
+
let device = self.createNativeDevice(from: peripheral)
|
|
182
|
+
resolve(device)
|
|
183
|
+
} else {
|
|
184
|
+
let error = self.createBleError(.DeviceNotFound, message: "Device not found: \(deviceIdentifier)")
|
|
185
|
+
reject(error)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public func devices(deviceIdentifiers: [String]) throws -> Promise<[NativeDevice]> {
|
|
191
|
+
return Promise.resolve(withBlock: {
|
|
192
|
+
return deviceIdentifiers.compactMap { identifier in
|
|
193
|
+
if let peripheral = self.discoveredDevices[identifier] ?? self.connectedDevices[identifier] {
|
|
194
|
+
return self.createNativeDevice(from: peripheral)
|
|
195
|
+
}
|
|
196
|
+
return nil
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public func connectedDevices(serviceUUIDs: [String]) throws -> Promise<[NativeDevice]> {
|
|
202
|
+
return Promise.resolve(withBlock: {
|
|
203
|
+
let serviceUUIDs = serviceUUIDs.compactMap { CBUUID(string: $0) }
|
|
204
|
+
let peripherals = self.centralManager.retrieveConnectedPeripherals(withServices: serviceUUIDs)
|
|
205
|
+
return peripherals.map { self.createNativeDevice(from: $0) }
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public func connectToDevice(deviceIdentifier: String, options: ConnectionOptions?) throws -> Promise<NativeDevice> {
|
|
210
|
+
return Promise.async { resolve, reject in
|
|
211
|
+
guard let peripheral = self.discoveredDevices[deviceIdentifier] else {
|
|
212
|
+
let error = self.createBleError(.DeviceNotFound, message: "Device not found: \(deviceIdentifier)")
|
|
213
|
+
reject(error)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
var connectOptions: [String: Any] = [:]
|
|
218
|
+
if let options = options {
|
|
219
|
+
connectOptions[CBConnectPeripheralOptionNotifyOnConnectionKey] = true
|
|
220
|
+
connectOptions[CBConnectPeripheralOptionNotifyOnDisconnectionKey] = true
|
|
221
|
+
connectOptions[CBConnectPeripheralOptionNotifyOnNotificationKey] = true
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
self.pendingOperations[deviceIdentifier] = resolve
|
|
225
|
+
self.centralManager.connect(peripheral, options: connectOptions)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public func cancelDeviceConnection(deviceIdentifier: String) throws -> Promise<NativeDevice> {
|
|
230
|
+
return Promise.async { resolve, reject in
|
|
231
|
+
guard let peripheral = self.connectedDevices[deviceIdentifier] else {
|
|
232
|
+
let error = self.createBleError(.DeviceNotFound, message: "Device not found: \(deviceIdentifier)")
|
|
233
|
+
reject(error)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
self.pendingOperations[deviceIdentifier] = resolve
|
|
238
|
+
self.centralManager.cancelPeripheralConnection(peripheral)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public func onDeviceDisconnected(deviceIdentifier: String, listener: @escaping (_ error: NativeBleError?, _ device: NativeDevice?) -> Void) throws -> Subscription {
|
|
243
|
+
self.deviceDisconnectListeners[deviceIdentifier] = listener
|
|
244
|
+
|
|
245
|
+
return SubscriptionImpl {
|
|
246
|
+
self.deviceDisconnectListeners.removeValue(forKey: deviceIdentifier)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public func isDeviceConnected(deviceIdentifier: String) throws -> Promise<Bool> {
|
|
251
|
+
return Promise.resolve(withBlock: {
|
|
252
|
+
return self.connectedDevices[deviceIdentifier] != nil
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Additional methods for service/characteristic operations would follow...
|
|
257
|
+
// For brevity, showing the core pattern. The full implementation would include
|
|
258
|
+
// all the remaining methods from the HybridBleManagerSpec protocol.
|
|
259
|
+
|
|
260
|
+
public func discoverAllServicesAndCharacteristicsForDevice(deviceIdentifier: String, transactionId: String?) throws -> Promise<NativeDevice> {
|
|
261
|
+
return Promise.async { resolve, reject in
|
|
262
|
+
guard let peripheral = self.connectedDevices[deviceIdentifier] else {
|
|
263
|
+
let error = self.createBleError(.DeviceNotFound, message: "Device not found: \(deviceIdentifier)")
|
|
264
|
+
reject(error)
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if let transactionId = transactionId {
|
|
269
|
+
self.pendingOperations[transactionId] = resolve
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
peripheral.discoverServices(nil)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public func servicesForDevice(deviceIdentifier: String) throws -> Promise<[NativeService]> {
|
|
277
|
+
return Promise.resolve(withBlock: {
|
|
278
|
+
guard let peripheral = self.connectedDevices[deviceIdentifier],
|
|
279
|
+
let services = peripheral.services else {
|
|
280
|
+
return []
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return services.enumerated().map { index, service in
|
|
284
|
+
return NativeService(
|
|
285
|
+
id: Double(index),
|
|
286
|
+
uuid: service.uuid.uuidString,
|
|
287
|
+
deviceID: deviceIdentifier,
|
|
288
|
+
isPrimary: service.isPrimary
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// MARK: - CBCentralManagerDelegate
|
|
295
|
+
|
|
296
|
+
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
297
|
+
let state = mapCBManagerState(central.state)
|
|
298
|
+
stateChangeListener?(state)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
|
302
|
+
let deviceId = peripheral.identifier.uuidString
|
|
303
|
+
discoveredDevices[deviceId] = peripheral
|
|
304
|
+
|
|
305
|
+
let device = createNativeDevice(from: peripheral, advertisementData: advertisementData, rssi: RSSI)
|
|
306
|
+
scanListener?(nil, device)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
|
310
|
+
let deviceId = peripheral.identifier.uuidString
|
|
311
|
+
connectedDevices[deviceId] = peripheral
|
|
312
|
+
peripheral.delegate = self
|
|
313
|
+
|
|
314
|
+
if let resolve = pendingOperations[deviceId] as? (NativeDevice) -> Void {
|
|
315
|
+
let device = createNativeDevice(from: peripheral)
|
|
316
|
+
resolve(device)
|
|
317
|
+
pendingOperations.removeValue(forKey: deviceId)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
|
322
|
+
let deviceId = peripheral.identifier.uuidString
|
|
323
|
+
connectedDevices.removeValue(forKey: deviceId)
|
|
324
|
+
|
|
325
|
+
let device = createNativeDevice(from: peripheral)
|
|
326
|
+
let bleError = error != nil ? createBleError(.DeviceDisconnected, message: error!.localizedDescription) : nil
|
|
327
|
+
|
|
328
|
+
deviceDisconnectListeners[deviceId]?(bleError, device)
|
|
329
|
+
deviceDisconnectListeners.removeValue(forKey: deviceId)
|
|
330
|
+
|
|
331
|
+
if let resolve = pendingOperations[deviceId] as? (NativeDevice) -> Void {
|
|
332
|
+
resolve(device)
|
|
333
|
+
pendingOperations.removeValue(forKey: deviceId)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
|
338
|
+
let deviceId = peripheral.identifier.uuidString
|
|
339
|
+
|
|
340
|
+
if let reject = pendingOperations[deviceId] as? (Error) -> Void {
|
|
341
|
+
let bleError = createBleError(.DeviceConnectionFailed, message: error?.localizedDescription ?? "Connection failed")
|
|
342
|
+
reject(bleError)
|
|
343
|
+
pendingOperations.removeValue(forKey: deviceId)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// MARK: - Helper Methods
|
|
348
|
+
|
|
349
|
+
private func mapCBManagerState(_ state: CBManagerState) -> State {
|
|
350
|
+
switch state {
|
|
351
|
+
case .unknown:
|
|
352
|
+
return .Unknown
|
|
353
|
+
case .resetting:
|
|
354
|
+
return .Resetting
|
|
355
|
+
case .unsupported:
|
|
356
|
+
return .Unsupported
|
|
357
|
+
case .unauthorized:
|
|
358
|
+
return .Unauthorized
|
|
359
|
+
case .poweredOff:
|
|
360
|
+
return .PoweredOff
|
|
361
|
+
case .poweredOn:
|
|
362
|
+
return .PoweredOn
|
|
363
|
+
@unknown default:
|
|
364
|
+
return .Unknown
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private func createNativeDevice(from peripheral: CBPeripheral, advertisementData: [String: Any]? = nil, rssi: NSNumber? = nil) -> NativeDevice {
|
|
369
|
+
var serviceData: [ServiceDataEntry] = []
|
|
370
|
+
var serviceUUIDs: [String] = []
|
|
371
|
+
var manufacturerData: String? = nil
|
|
372
|
+
var localName: String? = nil
|
|
373
|
+
var txPowerLevel: Double? = nil
|
|
374
|
+
var isConnectable: Bool? = nil
|
|
375
|
+
var solicitedServiceUUIDs: [String] = []
|
|
376
|
+
var overflowServiceUUIDs: [String] = []
|
|
377
|
+
|
|
378
|
+
if let adData = advertisementData {
|
|
379
|
+
// Parse advertisement data
|
|
380
|
+
if let services = adData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
|
|
381
|
+
serviceUUIDs = services.map { $0.uuidString }
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if let data = adData[CBAdvertisementDataManufacturerDataKey] as? Data {
|
|
385
|
+
manufacturerData = data.base64EncodedString()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
localName = adData[CBAdvertisementDataLocalNameKey] as? String
|
|
389
|
+
txPowerLevel = adData[CBAdvertisementDataTxPowerLevelKey] as? Double
|
|
390
|
+
isConnectable = adData[CBAdvertisementDataIsConnectable] as? Bool
|
|
391
|
+
|
|
392
|
+
if let services = adData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID] {
|
|
393
|
+
solicitedServiceUUIDs = services.map { $0.uuidString }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if let services = adData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID] {
|
|
397
|
+
overflowServiceUUIDs = services.map { $0.uuidString }
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if let serviceDataDict = adData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
|
|
401
|
+
serviceData = serviceDataDict.map { uuid, data in
|
|
402
|
+
ServiceDataEntry(uuid: uuid.uuidString, data: data.base64EncodedString())
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return NativeDevice(
|
|
408
|
+
id: peripheral.identifier.uuidString,
|
|
409
|
+
deviceName: peripheral.name ?? localName,
|
|
410
|
+
rssi: rssi?.doubleValue,
|
|
411
|
+
mtu: 23.0, // Default MTU on iOS
|
|
412
|
+
manufacturerData: manufacturerData,
|
|
413
|
+
serviceData: serviceData,
|
|
414
|
+
serviceUUIDs: serviceUUIDs.isEmpty ? nil : serviceUUIDs,
|
|
415
|
+
localName: localName,
|
|
416
|
+
txPowerLevel: txPowerLevel,
|
|
417
|
+
solicitedServiceUUIDs: solicitedServiceUUIDs.isEmpty ? nil : solicitedServiceUUIDs,
|
|
418
|
+
isConnectable: isConnectable,
|
|
419
|
+
overflowServiceUUIDs: overflowServiceUUIDs.isEmpty ? nil : overflowServiceUUIDs,
|
|
420
|
+
rawScanRecord: "" // iOS doesn't provide raw scan record
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private func createBleError(_ errorCode: BleErrorCode, message: String) -> NativeBleError {
|
|
425
|
+
return NativeBleError(
|
|
426
|
+
errorCode: errorCode,
|
|
427
|
+
attErrorCode: nil,
|
|
428
|
+
iosErrorCode: nil,
|
|
429
|
+
androidErrorCode: nil,
|
|
430
|
+
reason: message,
|
|
431
|
+
deviceID: nil,
|
|
432
|
+
serviceUUID: nil,
|
|
433
|
+
characteristicUUID: nil,
|
|
434
|
+
descriptorUUID: nil,
|
|
435
|
+
internalMessage: message
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// MARK: - CBPeripheralDelegate Extension
|
|
441
|
+
|
|
442
|
+
extension BleNitroBleManager: CBPeripheralDelegate {
|
|
443
|
+
|
|
444
|
+
public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
|
445
|
+
let deviceId = peripheral.identifier.uuidString
|
|
446
|
+
|
|
447
|
+
for (transactionId, operation) in pendingOperations {
|
|
448
|
+
if let resolve = operation as? (NativeDevice) -> Void {
|
|
449
|
+
let device = createNativeDevice(from: peripheral, rssi: RSSI)
|
|
450
|
+
resolve(device)
|
|
451
|
+
pendingOperations.removeValue(forKey: transactionId)
|
|
452
|
+
break
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
|
458
|
+
if let error = error {
|
|
459
|
+
// Handle service discovery error
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Discover characteristics for all services
|
|
464
|
+
peripheral.services?.forEach { service in
|
|
465
|
+
peripheral.discoverCharacteristics(nil, for: service)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
|
470
|
+
if let error = error {
|
|
471
|
+
// Handle characteristic discovery error
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Discover descriptors for all characteristics
|
|
476
|
+
service.characteristics?.forEach { characteristic in
|
|
477
|
+
peripheral.discoverDescriptors(for: characteristic)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
|
|
482
|
+
// Complete service discovery
|
|
483
|
+
let deviceId = peripheral.identifier.uuidString
|
|
484
|
+
|
|
485
|
+
for (transactionId, operation) in pendingOperations {
|
|
486
|
+
if let resolve = operation as? (NativeDevice) -> Void {
|
|
487
|
+
let device = createNativeDevice(from: peripheral)
|
|
488
|
+
resolve(device)
|
|
489
|
+
pendingOperations.removeValue(forKey: transactionId)
|
|
490
|
+
break
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Simple subscription implementation for cleanup
|
|
498
|
+
*/
|
|
499
|
+
private class SubscriptionImpl: Subscription {
|
|
500
|
+
private let cleanup: () -> Void
|
|
501
|
+
|
|
502
|
+
init(_ cleanup: @escaping () -> Void) {
|
|
503
|
+
self.cleanup = cleanup
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
public func remove() {
|
|
507
|
+
cleanup()
|
|
508
|
+
}
|
|
509
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// BleNitroModule.swift
|
|
3
|
+
/// React Native BLE Nitro - iOS Module Registration
|
|
4
|
+
/// Copyright © 2025 Zyke (https://zyke.co)
|
|
5
|
+
///
|
|
6
|
+
|
|
7
|
+
import Foundation
|
|
8
|
+
import NitroModules
|
|
9
|
+
|
|
10
|
+
@objc(BleNitroModule)
|
|
11
|
+
public class BleNitroModule: NSObject {
|
|
12
|
+
|
|
13
|
+
@objc
|
|
14
|
+
public static func requiresMainQueueSetup() -> Bool {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@objc
|
|
19
|
+
public func constantsToExport() -> [String: Any] {
|
|
20
|
+
return [:]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Nitro module factory for creating BLE manager instances
|
|
26
|
+
*/
|
|
27
|
+
@_cdecl("create_ble_nitro_manager")
|
|
28
|
+
public func createBleNitroManager() -> UnsafeMutableRawPointer {
|
|
29
|
+
let manager = BleNitroBleManager()
|
|
30
|
+
return Unmanaged.passRetained(manager).toOpaque()
|
|
31
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BleManager Compatibility Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates BleManager instances with full react-native-ble-plx compatibility
|
|
5
|
+
* by wrapping the Nitro implementation with compatibility shims
|
|
6
|
+
*/
|
|
7
|
+
import type { BleManagerOptions, UUID, DeviceId, TransactionId, ConnectionPriority, ConnectionOptions, ScanOptions, NativeService, NativeCharacteristic, NativeDescriptor, LogLevel, Subscription } from './specs/types';
|
|
8
|
+
import { DeviceWrapper } from './compatibility/deviceWrapper';
|
|
9
|
+
/**
|
|
10
|
+
* BleManager wrapper that provides react-native-ble-plx compatibility
|
|
11
|
+
*/
|
|
12
|
+
export declare class BleManagerCompat {
|
|
13
|
+
private bleManager;
|
|
14
|
+
constructor(options?: BleManagerOptions);
|
|
15
|
+
destroy(): Promise<void>;
|
|
16
|
+
setLogLevel(logLevel: LogLevel | string): Promise<string>;
|
|
17
|
+
logLevel(): Promise<string>;
|
|
18
|
+
cancelTransaction(transactionId: TransactionId): Promise<void>;
|
|
19
|
+
enable(transactionId?: TransactionId): Promise<BleManagerCompat>;
|
|
20
|
+
disable(transactionId?: TransactionId): Promise<BleManagerCompat>;
|
|
21
|
+
state(): Promise<string>;
|
|
22
|
+
onStateChange(listener: (newState: string) => void, emitCurrentState?: boolean): Subscription;
|
|
23
|
+
startDeviceScan(uuids: UUID[] | null, options: ScanOptions | null, listener: (error: any | null, scannedDevice: DeviceWrapper | null) => void): Promise<void>;
|
|
24
|
+
stopDeviceScan(): Promise<void>;
|
|
25
|
+
connectToDevice(deviceIdentifier: DeviceId, options?: Partial<ConnectionOptions>): Promise<DeviceWrapper>;
|
|
26
|
+
cancelDeviceConnection(deviceIdentifier: DeviceId): Promise<DeviceWrapper>;
|
|
27
|
+
isDeviceConnected(deviceIdentifier: DeviceId): Promise<boolean>;
|
|
28
|
+
onDeviceDisconnected(deviceIdentifier: DeviceId, listener: (error: any | null, device: DeviceWrapper | null) => void): Subscription;
|
|
29
|
+
devices(deviceIdentifiers: DeviceId[]): Promise<DeviceWrapper[]>;
|
|
30
|
+
connectedDevices(serviceUUIDs: UUID[]): Promise<DeviceWrapper[]>;
|
|
31
|
+
readRSSIForDevice(deviceIdentifier: DeviceId, transactionId?: TransactionId): Promise<DeviceWrapper>;
|
|
32
|
+
requestMTUForDevice(deviceIdentifier: DeviceId, mtu: number, transactionId?: TransactionId): Promise<DeviceWrapper>;
|
|
33
|
+
requestConnectionPriorityForDevice(deviceIdentifier: DeviceId, connectionPriority: ConnectionPriority, transactionId?: TransactionId): Promise<DeviceWrapper>;
|
|
34
|
+
discoverAllServicesAndCharacteristicsForDevice(deviceIdentifier: DeviceId, transactionId?: TransactionId): Promise<DeviceWrapper>;
|
|
35
|
+
servicesForDevice(deviceIdentifier: DeviceId): Promise<NativeService[]>;
|
|
36
|
+
characteristicsForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID): Promise<NativeCharacteristic[]>;
|
|
37
|
+
readCharacteristicForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, transactionId?: TransactionId): Promise<NativeCharacteristic>;
|
|
38
|
+
writeCharacteristicWithResponseForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, base64Value: string, transactionId?: TransactionId): Promise<NativeCharacteristic>;
|
|
39
|
+
writeCharacteristicWithoutResponseForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, base64Value: string, transactionId?: TransactionId): Promise<NativeCharacteristic>;
|
|
40
|
+
monitorCharacteristicForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, listener: (error: any | null, characteristic: NativeCharacteristic | null) => void, transactionId?: TransactionId, subscriptionType?: 'notification' | 'indication'): Subscription;
|
|
41
|
+
descriptorsForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID): Promise<NativeDescriptor[]>;
|
|
42
|
+
readDescriptorForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID, transactionId?: TransactionId): Promise<NativeDescriptor>;
|
|
43
|
+
writeDescriptorForDevice(deviceIdentifier: DeviceId, serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID, valueBase64: string, transactionId?: TransactionId): Promise<NativeDescriptor>;
|
|
44
|
+
/**
|
|
45
|
+
* Helper method to create a Device wrapper from NativeDevice data
|
|
46
|
+
* This is a temporary method until we have proper Device Nitro objects
|
|
47
|
+
*/
|
|
48
|
+
private createDeviceFromNative;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Factory function to create a compatibility BleManager
|
|
52
|
+
*/
|
|
53
|
+
export declare function createBleManagerCompat(options?: BleManagerOptions): BleManagerCompat;
|