munim-bluetooth 0.1.0 → 0.2.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.
@@ -1,14 +1,633 @@
1
1
  //
2
2
  // HybridMunimBluetooth.swift
3
- // Pods
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
- func sum(num1: Double, num2: Double) throws -> Double {
12
- return num1 + num2
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
  }