munim-bluetooth 0.1.0 → 0.2.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 +769 -15
- package/android/src/main/AndroidManifest.xml +18 -0
- package/android/src/main/java/com/munimbluetooth/HybridMunimBluetooth.kt +700 -3
- package/ios/HybridMunimBluetooth.swift +622 -3
- package/lib/commonjs/index.js +247 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +227 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +161 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/munim-bluetooth.nitro.d.ts +193 -1
- package/lib/typescript/src/specs/munim-bluetooth.nitro.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/index.ts +287 -3
- package/src/specs/munim-bluetooth.nitro.ts +266 -3
|
@@ -1,14 +1,633 @@
|
|
|
1
1
|
//
|
|
2
2
|
// HybridMunimBluetooth.swift
|
|
3
|
-
//
|
|
3
|
+
// munim-bluetooth
|
|
4
4
|
//
|
|
5
5
|
// Created by sheehanmunim on 11/12/2025.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
import Foundation
|
|
9
|
+
import CoreBluetooth
|
|
10
|
+
import ReactNativeNitroModules
|
|
9
11
|
|
|
10
12
|
class HybridMunimBluetooth: HybridMunimBluetoothSpec {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// Peripheral Manager
|
|
14
|
+
private var peripheralManager: CBPeripheralManager?
|
|
15
|
+
private var peripheralServices: [CBMutableService] = []
|
|
16
|
+
private var currentAdvertisingData: [String: Any] = [:]
|
|
17
|
+
|
|
18
|
+
// Central Manager
|
|
19
|
+
private var centralManager: CBCentralManager?
|
|
20
|
+
private var discoveredPeripherals: [String: CBPeripheral] = [:]
|
|
21
|
+
private var connectedPeripherals: [String: CBPeripheral] = [:]
|
|
22
|
+
private var peripheralCharacteristics: [String: [CBCharacteristic]] = [:]
|
|
23
|
+
private var scanOptions: [String: Any]?
|
|
24
|
+
private var isScanning = false
|
|
25
|
+
|
|
26
|
+
// Event emitter
|
|
27
|
+
private var eventEmitter: NitroEventEmitter?
|
|
28
|
+
|
|
29
|
+
override init() {
|
|
30
|
+
super.init()
|
|
31
|
+
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
|
|
32
|
+
centralManager = CBCentralManager(delegate: self, queue: nil)
|
|
33
|
+
eventEmitter = NitroEventEmitter(moduleName: "MunimBluetooth")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// MARK: - Peripheral Features
|
|
37
|
+
|
|
38
|
+
func startAdvertising(options: [String: Any]) throws {
|
|
39
|
+
guard let peripheralManager = peripheralManager,
|
|
40
|
+
peripheralManager.state == .poweredOn else {
|
|
41
|
+
throw NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Bluetooth is not powered on"])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var advertisingData: [String: Any] = [:]
|
|
45
|
+
|
|
46
|
+
// Process comprehensive advertising data
|
|
47
|
+
if let advertisingDataDict = options["advertisingData"] as? [String: Any] {
|
|
48
|
+
processAdvertisingData(advertisingDataDict, into: &advertisingData)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Legacy support
|
|
52
|
+
if let localName = options["localName"] as? String {
|
|
53
|
+
advertisingData[CBAdvertisementDataLocalNameKey] = localName
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if let serviceUUIDs = options["serviceUUIDs"] as? [String], !serviceUUIDs.isEmpty {
|
|
57
|
+
let uuids = serviceUUIDs.compactMap { CBUUID(string: $0) }
|
|
58
|
+
advertisingData[CBAdvertisementDataServiceUUIDsKey] = uuids
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if let manufacturerData = options["manufacturerData"] as? String,
|
|
62
|
+
let data = hexStringToData(manufacturerData) {
|
|
63
|
+
advertisingData[CBAdvertisementDataManufacturerDataKey] = data
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
currentAdvertisingData = advertisingData
|
|
67
|
+
peripheralManager.startAdvertising(advertisingData as? [String: Any])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func updateAdvertisingData(advertisingData: [String: Any]) throws {
|
|
71
|
+
guard let peripheralManager = peripheralManager,
|
|
72
|
+
peripheralManager.state == .poweredOn else {
|
|
73
|
+
throw NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Bluetooth is not powered on"])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
peripheralManager.stopAdvertising()
|
|
77
|
+
|
|
78
|
+
var newAdvertisingData: [String: Any] = [:]
|
|
79
|
+
processAdvertisingData(advertisingData, into: &newAdvertisingData)
|
|
80
|
+
|
|
81
|
+
currentAdvertisingData = newAdvertisingData
|
|
82
|
+
peripheralManager.startAdvertising(newAdvertisingData as? [String: Any])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func getAdvertisingData() throws -> [String: Any] {
|
|
86
|
+
return currentAdvertisingData
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func stopAdvertising() throws {
|
|
90
|
+
peripheralManager?.stopAdvertising()
|
|
91
|
+
currentAdvertisingData = [:]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func setServices(services: [[String: Any]]) throws {
|
|
95
|
+
peripheralServices.removeAll()
|
|
96
|
+
|
|
97
|
+
for serviceDict in services {
|
|
98
|
+
guard let uuidString = serviceDict["uuid"] as? String else { continue }
|
|
99
|
+
|
|
100
|
+
let serviceUUID = CBUUID(string: uuidString)
|
|
101
|
+
let service = CBMutableService(type: serviceUUID, primary: true)
|
|
102
|
+
|
|
103
|
+
var characteristics: [CBMutableCharacteristic] = []
|
|
104
|
+
|
|
105
|
+
if let characteristicsArray = serviceDict["characteristics"] as? [[String: Any]] {
|
|
106
|
+
for charDict in characteristicsArray {
|
|
107
|
+
guard let charUUIDString = charDict["uuid"] as? String else { continue }
|
|
108
|
+
|
|
109
|
+
let charUUID = CBUUID(string: charUUIDString)
|
|
110
|
+
var properties: CBCharacteristicProperties = []
|
|
111
|
+
|
|
112
|
+
if let propertiesArray = charDict["properties"] as? [String] {
|
|
113
|
+
for prop in propertiesArray {
|
|
114
|
+
switch prop {
|
|
115
|
+
case "read":
|
|
116
|
+
properties.insert(.read)
|
|
117
|
+
case "write":
|
|
118
|
+
properties.insert(.write)
|
|
119
|
+
case "notify":
|
|
120
|
+
properties.insert(.notify)
|
|
121
|
+
case "indicate":
|
|
122
|
+
properties.insert(.indicate)
|
|
123
|
+
default:
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var value: Data?
|
|
130
|
+
if let valueString = charDict["value"] as? String {
|
|
131
|
+
value = valueString.data(using: .utf8)
|
|
132
|
+
properties.insert(.read)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let permissions: CBAttributePermissions = value != nil ? .readable : [.readable, .writeable]
|
|
136
|
+
|
|
137
|
+
let characteristic = CBMutableCharacteristic(
|
|
138
|
+
type: charUUID,
|
|
139
|
+
properties: properties,
|
|
140
|
+
value: value,
|
|
141
|
+
permissions: permissions
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
characteristics.append(characteristic)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
service.characteristics = characteristics
|
|
149
|
+
peripheralServices.append(service)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
peripheralManager?.removeAllServices()
|
|
153
|
+
for service in peripheralServices {
|
|
154
|
+
peripheralManager?.add(service)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// MARK: - Central Features
|
|
159
|
+
|
|
160
|
+
func isBluetoothEnabled() throws -> Bool {
|
|
161
|
+
guard let centralManager = centralManager else { return false }
|
|
162
|
+
return centralManager.state == .poweredOn
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func requestBluetoothPermission() throws -> Bool {
|
|
166
|
+
guard let centralManager = centralManager else { return false }
|
|
167
|
+
// On iOS, permission is requested automatically when creating CBCentralManager
|
|
168
|
+
// The state will be .unauthorized if permission is denied
|
|
169
|
+
return centralManager.state != .unauthorized
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func startScan(options: [String: Any]?) throws {
|
|
173
|
+
guard let centralManager = centralManager,
|
|
174
|
+
centralManager.state == .poweredOn else {
|
|
175
|
+
throw NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Bluetooth is not powered on"])
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
guard !isScanning else { return }
|
|
179
|
+
|
|
180
|
+
scanOptions = options
|
|
181
|
+
isScanning = true
|
|
182
|
+
discoveredPeripherals.removeAll()
|
|
183
|
+
|
|
184
|
+
var serviceUUIDs: [CBUUID]?
|
|
185
|
+
if let serviceUUIDsArray = options?["serviceUUIDs"] as? [String] {
|
|
186
|
+
serviceUUIDs = serviceUUIDsArray.compactMap { CBUUID(string: $0) }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
centralManager.scanForPeripherals(withServices: serviceUUIDs, options: [
|
|
190
|
+
CBCentralManagerScanOptionAllowDuplicatesKey: options?["allowDuplicates"] as? Bool ?? false
|
|
191
|
+
])
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
func stopScan() throws {
|
|
195
|
+
guard isScanning else { return }
|
|
196
|
+
centralManager?.stopScan()
|
|
197
|
+
isScanning = false
|
|
198
|
+
scanOptions = nil
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
func connect(deviceId: String) throws {
|
|
202
|
+
guard let peripheral = discoveredPeripherals[deviceId] else {
|
|
203
|
+
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not found"])
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
centralManager?.connect(peripheral, options: nil)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func disconnect(deviceId: String) throws {
|
|
210
|
+
guard let peripheral = connectedPeripherals[deviceId] ?? discoveredPeripherals[deviceId] else {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
centralManager?.cancelPeripheralConnection(peripheral)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
func discoverServices(deviceId: String) throws -> [[String: Any]] {
|
|
218
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
219
|
+
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
peripheral.discoverServices(nil)
|
|
223
|
+
|
|
224
|
+
// Wait for services to be discovered (in real implementation, this would be async)
|
|
225
|
+
// For now, return empty array - services will be discovered via delegate callbacks
|
|
226
|
+
return []
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func readCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws -> [String: Any] {
|
|
230
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
231
|
+
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
235
|
+
let characteristic = characteristics.first(where: {
|
|
236
|
+
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
237
|
+
$0.uuid == CBUUID(string: characteristicUUID)
|
|
238
|
+
}) else {
|
|
239
|
+
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
peripheral.readValue(for: characteristic)
|
|
243
|
+
|
|
244
|
+
// Return empty for now - value will come via delegate callback
|
|
245
|
+
return [
|
|
246
|
+
"value": "",
|
|
247
|
+
"serviceUUID": serviceUUID,
|
|
248
|
+
"characteristicUUID": characteristicUUID
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func writeCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String, value: String, writeType: String?) throws {
|
|
253
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
254
|
+
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
258
|
+
let characteristic = characteristics.first(where: {
|
|
259
|
+
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
260
|
+
$0.uuid == CBUUID(string: characteristicUUID)
|
|
261
|
+
}) else {
|
|
262
|
+
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
guard let data = hexStringToData(value) else {
|
|
266
|
+
throw NSError(domain: "MunimBluetooth", code: 4, userInfo: [NSLocalizedDescriptionKey: "Invalid hex string"])
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let type: CBCharacteristicWriteType = (writeType == "writeWithoutResponse") ? .withoutResponse : .withResponse
|
|
270
|
+
peripheral.writeValue(data, for: characteristic, type: type)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
func subscribeToCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws {
|
|
274
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
275
|
+
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
279
|
+
let characteristic = characteristics.first(where: {
|
|
280
|
+
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
281
|
+
$0.uuid == CBUUID(string: characteristicUUID)
|
|
282
|
+
}) else {
|
|
283
|
+
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
peripheral.setNotifyValue(true, for: characteristic)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func unsubscribeFromCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws {
|
|
290
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
291
|
+
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
295
|
+
let characteristic = characteristics.first(where: {
|
|
296
|
+
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
297
|
+
$0.uuid == CBUUID(string: characteristicUUID)
|
|
298
|
+
}) else {
|
|
299
|
+
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
peripheral.setNotifyValue(false, for: characteristic)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
func getConnectedDevices() throws -> [String] {
|
|
306
|
+
return Array(connectedPeripherals.keys)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
func readRSSI(deviceId: String) throws -> Double {
|
|
310
|
+
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
311
|
+
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
peripheral.readRSSI()
|
|
315
|
+
// RSSI will come via delegate callback
|
|
316
|
+
return 0.0
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// MARK: - Event Management
|
|
320
|
+
|
|
321
|
+
func addListener(eventName: String) throws {
|
|
322
|
+
// Event listeners are handled by the event emitter
|
|
323
|
+
// This is a no-op as Nitro modules handle events differently
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
func removeListeners(count: Double) throws {
|
|
327
|
+
// Event listeners are handled by the event emitter
|
|
328
|
+
// This is a no-op as Nitro modules handle events differently
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// MARK: - Helper Methods
|
|
332
|
+
|
|
333
|
+
private func processAdvertisingData(_ dataDict: [String: Any], into advertisingData: inout [String: Any]) {
|
|
334
|
+
// Flags
|
|
335
|
+
if let flags = dataDict["flags"] as? Int {
|
|
336
|
+
let isConnectable = (flags & 0x02) != 0
|
|
337
|
+
advertisingData[CBAdvertisementDataIsConnectable] = isConnectable
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Service UUIDs
|
|
341
|
+
addServiceUUIDs(dataDict["incompleteServiceUUIDs16"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
342
|
+
addServiceUUIDs(dataDict["completeServiceUUIDs16"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
343
|
+
addServiceUUIDs(dataDict["incompleteServiceUUIDs32"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
344
|
+
addServiceUUIDs(dataDict["completeServiceUUIDs32"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
345
|
+
addServiceUUIDs(dataDict["incompleteServiceUUIDs128"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
346
|
+
addServiceUUIDs(dataDict["completeServiceUUIDs128"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
347
|
+
|
|
348
|
+
// Local Name
|
|
349
|
+
if let shortenedName = dataDict["shortenedLocalName"] as? String {
|
|
350
|
+
advertisingData[CBAdvertisementDataLocalNameKey] = shortenedName
|
|
351
|
+
}
|
|
352
|
+
if let completeName = dataDict["completeLocalName"] as? String {
|
|
353
|
+
advertisingData[CBAdvertisementDataLocalNameKey] = completeName
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Tx Power Level
|
|
357
|
+
if let txPower = dataDict["txPowerLevel"] as? Int {
|
|
358
|
+
advertisingData[CBAdvertisementDataTxPowerLevelKey] = txPower
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Service Solicitation
|
|
362
|
+
addServiceUUIDs(dataDict["serviceSolicitationUUIDs16"], to: &advertisingData, key: CBAdvertisementDataSolicitedServiceUUIDsKey)
|
|
363
|
+
addServiceUUIDs(dataDict["serviceSolicitationUUIDs128"], to: &advertisingData, key: CBAdvertisementDataSolicitedServiceUUIDsKey)
|
|
364
|
+
addServiceUUIDs(dataDict["serviceSolicitationUUIDs32"], to: &advertisingData, key: CBAdvertisementDataSolicitedServiceUUIDsKey)
|
|
365
|
+
|
|
366
|
+
// Service Data
|
|
367
|
+
addServiceData(dataDict["serviceData16"], to: &advertisingData)
|
|
368
|
+
addServiceData(dataDict["serviceData32"], to: &advertisingData)
|
|
369
|
+
addServiceData(dataDict["serviceData128"], to: &advertisingData)
|
|
370
|
+
|
|
371
|
+
// Appearance
|
|
372
|
+
if let appearance = dataDict["appearance"] as? Int {
|
|
373
|
+
let appearanceData = Data(bytes: [UInt8(appearance & 0xFF), UInt8((appearance >> 8) & 0xFF)], count: 2)
|
|
374
|
+
advertisingData[CBUUID(string: "1800")] = appearanceData
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Manufacturer Data
|
|
378
|
+
if let manufacturerData = dataDict["manufacturerData"] as? String,
|
|
379
|
+
let data = hexStringToData(manufacturerData) {
|
|
380
|
+
advertisingData[CBAdvertisementDataManufacturerDataKey] = data
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private func addServiceUUIDs(_ uuids: Any?, to advertisingData: inout [String: Any], key: String) {
|
|
385
|
+
guard let uuidArray = uuids as? [String] else { return }
|
|
386
|
+
|
|
387
|
+
let cbUUIDs = uuidArray.compactMap { CBUUID(string: $0) }
|
|
388
|
+
if !cbUUIDs.isEmpty {
|
|
389
|
+
if var existing = advertisingData[key] as? [CBUUID] {
|
|
390
|
+
existing.append(contentsOf: cbUUIDs)
|
|
391
|
+
advertisingData[key] = existing
|
|
392
|
+
} else {
|
|
393
|
+
advertisingData[key] = cbUUIDs
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private func addServiceData(_ serviceDataArray: Any?, to advertisingData: inout [String: Any]) {
|
|
399
|
+
guard let array = serviceDataArray as? [[String: Any]] else { return }
|
|
400
|
+
|
|
401
|
+
for serviceData in array {
|
|
402
|
+
guard let uuid = serviceData["uuid"] as? String,
|
|
403
|
+
let dataString = serviceData["data"] as? String,
|
|
404
|
+
let data = hexStringToData(dataString) else { continue }
|
|
405
|
+
|
|
406
|
+
advertisingData[uuid] = data
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private func hexStringToData(_ hexString: String) -> Data? {
|
|
411
|
+
let cleanHex = hexString.replacingOccurrences(of: " ", with: "")
|
|
412
|
+
guard cleanHex.count % 2 == 0 else { return nil }
|
|
413
|
+
|
|
414
|
+
var data = Data()
|
|
415
|
+
var index = cleanHex.startIndex
|
|
416
|
+
|
|
417
|
+
while index < cleanHex.endIndex {
|
|
418
|
+
let nextIndex = cleanHex.index(index, offsetBy: 2)
|
|
419
|
+
guard nextIndex <= cleanHex.endIndex else { break }
|
|
420
|
+
|
|
421
|
+
let byteString = String(cleanHex[index..<nextIndex])
|
|
422
|
+
if let byte = UInt8(byteString, radix: 16) {
|
|
423
|
+
data.append(byte)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
index = nextIndex
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return data
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// MARK: - CBPeripheralManagerDelegate
|
|
434
|
+
|
|
435
|
+
extension HybridMunimBluetooth: CBPeripheralManagerDelegate {
|
|
436
|
+
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
|
|
437
|
+
// Handle state updates
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
|
|
441
|
+
if let error = error {
|
|
442
|
+
// Emit error event
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
|
|
447
|
+
if let error = error {
|
|
448
|
+
// Emit error event
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// MARK: - CBCentralManagerDelegate
|
|
454
|
+
|
|
455
|
+
extension HybridMunimBluetooth: CBCentralManagerDelegate {
|
|
456
|
+
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
457
|
+
// Handle state updates
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
|
461
|
+
let deviceId = peripheral.identifier.uuidString
|
|
462
|
+
discoveredPeripherals[deviceId] = peripheral
|
|
463
|
+
|
|
464
|
+
// Emit deviceFound event
|
|
465
|
+
eventEmitter?.emit("deviceFound", [
|
|
466
|
+
"id": deviceId,
|
|
467
|
+
"name": peripheral.name ?? "",
|
|
468
|
+
"rssi": RSSI.intValue,
|
|
469
|
+
"advertisingData": advertisementData
|
|
470
|
+
])
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
|
474
|
+
let deviceId = peripheral.identifier.uuidString
|
|
475
|
+
connectedPeripherals[deviceId] = peripheral
|
|
476
|
+
peripheral.delegate = self
|
|
477
|
+
|
|
478
|
+
// Emit deviceConnected event
|
|
479
|
+
eventEmitter?.emit("deviceConnected", ["id": deviceId])
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
|
483
|
+
let deviceId = peripheral.identifier.uuidString
|
|
484
|
+
connectedPeripherals.removeValue(forKey: deviceId)
|
|
485
|
+
peripheralCharacteristics.removeValue(forKey: deviceId)
|
|
486
|
+
|
|
487
|
+
// Emit deviceDisconnected event
|
|
488
|
+
eventEmitter?.emit("deviceDisconnected", ["id": deviceId])
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
|
492
|
+
let deviceId = peripheral.identifier.uuidString
|
|
493
|
+
|
|
494
|
+
// Emit connectionFailed event
|
|
495
|
+
eventEmitter?.emit("connectionFailed", [
|
|
496
|
+
"id": deviceId,
|
|
497
|
+
"error": error?.localizedDescription ?? "Unknown error"
|
|
498
|
+
])
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// MARK: - CBPeripheralDelegate
|
|
503
|
+
|
|
504
|
+
extension HybridMunimBluetooth: CBPeripheralDelegate {
|
|
505
|
+
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
|
506
|
+
let deviceId = peripheral.identifier.uuidString
|
|
507
|
+
|
|
508
|
+
guard let services = peripheral.services else { return }
|
|
509
|
+
|
|
510
|
+
var servicesArray: [[String: Any]] = []
|
|
511
|
+
for service in services {
|
|
512
|
+
peripheral.discoverCharacteristics(nil, for: service)
|
|
513
|
+
servicesArray.append(["uuid": service.uuid.uuidString])
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Emit servicesDiscovered event
|
|
517
|
+
eventEmitter?.emit("servicesDiscovered", [
|
|
518
|
+
"id": deviceId,
|
|
519
|
+
"services": servicesArray
|
|
520
|
+
])
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
|
524
|
+
let deviceId = peripheral.identifier.uuidString
|
|
525
|
+
|
|
526
|
+
guard let characteristics = service.characteristics else { return }
|
|
527
|
+
|
|
528
|
+
if peripheralCharacteristics[deviceId] == nil {
|
|
529
|
+
peripheralCharacteristics[deviceId] = []
|
|
530
|
+
}
|
|
531
|
+
peripheralCharacteristics[deviceId]?.append(contentsOf: characteristics)
|
|
532
|
+
|
|
533
|
+
// Emit characteristicsDiscovered event
|
|
534
|
+
var characteristicsArray: [[String: Any]] = []
|
|
535
|
+
for characteristic in characteristics {
|
|
536
|
+
var properties: [String] = []
|
|
537
|
+
if characteristic.properties.contains(.read) { properties.append("read") }
|
|
538
|
+
if characteristic.properties.contains(.write) { properties.append("write") }
|
|
539
|
+
if characteristic.properties.contains(.notify) { properties.append("notify") }
|
|
540
|
+
if characteristic.properties.contains(.indicate) { properties.append("indicate") }
|
|
541
|
+
|
|
542
|
+
characteristicsArray.append([
|
|
543
|
+
"uuid": characteristic.uuid.uuidString,
|
|
544
|
+
"properties": properties
|
|
545
|
+
])
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
eventEmitter?.emit("characteristicsDiscovered", [
|
|
549
|
+
"id": deviceId,
|
|
550
|
+
"serviceUUID": service.uuid.uuidString,
|
|
551
|
+
"characteristics": characteristicsArray
|
|
552
|
+
])
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
556
|
+
let deviceId = peripheral.identifier.uuidString
|
|
557
|
+
|
|
558
|
+
guard let data = characteristic.value else { return }
|
|
559
|
+
|
|
560
|
+
let hexString = data.map { String(format: "%02x", $0) }.joined()
|
|
561
|
+
|
|
562
|
+
// Emit characteristicValueChanged event
|
|
563
|
+
eventEmitter?.emit("characteristicValueChanged", [
|
|
564
|
+
"id": deviceId,
|
|
565
|
+
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
566
|
+
"characteristicUUID": characteristic.uuid.uuidString,
|
|
567
|
+
"value": hexString
|
|
568
|
+
])
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
572
|
+
let deviceId = peripheral.identifier.uuidString
|
|
573
|
+
|
|
574
|
+
if let error = error {
|
|
575
|
+
eventEmitter?.emit("writeError", [
|
|
576
|
+
"id": deviceId,
|
|
577
|
+
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
578
|
+
"characteristicUUID": characteristic.uuid.uuidString,
|
|
579
|
+
"error": error.localizedDescription
|
|
580
|
+
])
|
|
581
|
+
} else {
|
|
582
|
+
eventEmitter?.emit("writeSuccess", [
|
|
583
|
+
"id": deviceId,
|
|
584
|
+
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
585
|
+
"characteristicUUID": characteristic.uuid.uuidString
|
|
586
|
+
])
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
|
591
|
+
let deviceId = peripheral.identifier.uuidString
|
|
592
|
+
|
|
593
|
+
if let error = error {
|
|
594
|
+
eventEmitter?.emit("subscriptionError", [
|
|
595
|
+
"id": deviceId,
|
|
596
|
+
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
597
|
+
"characteristicUUID": characteristic.uuid.uuidString,
|
|
598
|
+
"error": error.localizedDescription
|
|
599
|
+
])
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
|
604
|
+
let deviceId = peripheral.identifier.uuidString
|
|
605
|
+
|
|
606
|
+
if let error = error {
|
|
607
|
+
eventEmitter?.emit("rssiError", [
|
|
608
|
+
"id": deviceId,
|
|
609
|
+
"error": error.localizedDescription
|
|
610
|
+
])
|
|
611
|
+
} else {
|
|
612
|
+
eventEmitter?.emit("rssiUpdated", [
|
|
613
|
+
"id": deviceId,
|
|
614
|
+
"rssi": RSSI.intValue
|
|
615
|
+
])
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// MARK: - Event Emitter Helper
|
|
621
|
+
|
|
622
|
+
class NitroEventEmitter {
|
|
623
|
+
private let moduleName: String
|
|
624
|
+
|
|
625
|
+
init(moduleName: String) {
|
|
626
|
+
self.moduleName = moduleName
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
func emit(_ eventName: String, _ body: [String: Any]) {
|
|
630
|
+
// Nitro modules event emission would be handled here
|
|
631
|
+
// This is a placeholder for the actual implementation
|
|
13
632
|
}
|
|
14
633
|
}
|