@capacitor-community/bluetooth-le 8.0.1 → 8.0.2

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.
Files changed (52) hide show
  1. package/CapacitorCommunityBluetoothLe.podspec +17 -17
  2. package/LICENSE +21 -21
  3. package/Package.swift +27 -27
  4. package/README.md +2 -1
  5. package/android/build.gradle +73 -73
  6. package/android/src/main/AndroidManifest.xml +22 -22
  7. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt +1094 -1094
  8. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt +51 -51
  9. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Device.kt +771 -771
  10. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceList.kt +28 -28
  11. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt +189 -189
  12. package/dist/docs.json +43 -43
  13. package/dist/esm/bleClient.d.ts +278 -278
  14. package/dist/esm/bleClient.js +361 -361
  15. package/dist/esm/bleClient.js.map +1 -1
  16. package/dist/esm/config.d.ts +53 -53
  17. package/dist/esm/config.js +2 -2
  18. package/dist/esm/conversion.d.ts +56 -56
  19. package/dist/esm/conversion.js +134 -134
  20. package/dist/esm/conversion.js.map +1 -1
  21. package/dist/esm/definitions.d.ts +352 -352
  22. package/dist/esm/definitions.js +42 -42
  23. package/dist/esm/definitions.js.map +1 -1
  24. package/dist/esm/index.d.ts +5 -5
  25. package/dist/esm/index.js +5 -5
  26. package/dist/esm/plugin.d.ts +2 -2
  27. package/dist/esm/plugin.js +4 -4
  28. package/dist/esm/queue.d.ts +3 -3
  29. package/dist/esm/queue.js +17 -17
  30. package/dist/esm/timeout.d.ts +1 -1
  31. package/dist/esm/timeout.js +9 -9
  32. package/dist/esm/validators.d.ts +1 -1
  33. package/dist/esm/validators.js +11 -11
  34. package/dist/esm/web.d.ts +57 -57
  35. package/dist/esm/web.js +403 -403
  36. package/dist/esm/web.js.map +1 -1
  37. package/dist/plugin.cjs.js +964 -964
  38. package/dist/plugin.cjs.js.map +1 -1
  39. package/dist/plugin.js +964 -964
  40. package/dist/plugin.js.map +1 -1
  41. package/ios/Sources/BluetoothLe/Conversion.swift +83 -83
  42. package/ios/Sources/BluetoothLe/Device.swift +422 -423
  43. package/ios/Sources/BluetoothLe/DeviceListView.swift +121 -121
  44. package/ios/Sources/BluetoothLe/DeviceManager.swift +409 -415
  45. package/ios/Sources/BluetoothLe/Logging.swift +8 -8
  46. package/ios/Sources/BluetoothLe/Plugin.swift +768 -763
  47. package/ios/Sources/BluetoothLe/ScanFilters.swift +114 -114
  48. package/ios/Sources/BluetoothLe/ThreadSafeDictionary.swift +61 -15
  49. package/ios/Tests/BluetoothLeTests/ConversionTests.swift +55 -55
  50. package/ios/Tests/BluetoothLeTests/PluginTests.swift +27 -27
  51. package/ios/Tests/BluetoothLeTests/ScanFiltersTests.swift +153 -153
  52. package/package.json +114 -114
@@ -1,763 +1,768 @@
1
- // swiftlint:disable identifier_name
2
- // swiftlint:disable type_body_length
3
- import Foundation
4
- import Capacitor
5
- import CoreBluetooth
6
-
7
- let CONNECTION_TIMEOUT: Double = 10
8
- let DEFAULT_TIMEOUT: Double = 5
9
-
10
- @objc(BluetoothLe)
11
- public class BluetoothLe: CAPPlugin, CAPBridgedPlugin {
12
- public let identifier = "BluetoothLe"
13
- public let jsName = "BluetoothLe"
14
- public let pluginMethods: [CAPPluginMethod] = [
15
- CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise),
16
- CAPPluginMethod(name: "isEnabled", returnType: CAPPluginReturnPromise),
17
- CAPPluginMethod(name: "requestEnable", returnType: CAPPluginReturnPromise),
18
- CAPPluginMethod(name: "enable", returnType: CAPPluginReturnPromise),
19
- CAPPluginMethod(name: "disable", returnType: CAPPluginReturnPromise),
20
- CAPPluginMethod(name: "startEnabledNotifications", returnType: CAPPluginReturnPromise),
21
- CAPPluginMethod(name: "stopEnabledNotifications", returnType: CAPPluginReturnPromise),
22
- CAPPluginMethod(name: "isLocationEnabled", returnType: CAPPluginReturnPromise),
23
- CAPPluginMethod(name: "openLocationSettings", returnType: CAPPluginReturnPromise),
24
- CAPPluginMethod(name: "openBluetoothSettings", returnType: CAPPluginReturnPromise),
25
- CAPPluginMethod(name: "openAppSettings", returnType: CAPPluginReturnPromise),
26
- CAPPluginMethod(name: "setDisplayStrings", returnType: CAPPluginReturnPromise),
27
- CAPPluginMethod(name: "requestDevice", returnType: CAPPluginReturnPromise),
28
- CAPPluginMethod(name: "requestLEScan", returnType: CAPPluginReturnPromise),
29
- CAPPluginMethod(name: "stopLEScan", returnType: CAPPluginReturnPromise),
30
- CAPPluginMethod(name: "getDevices", returnType: CAPPluginReturnPromise),
31
- CAPPluginMethod(name: "discoverServices", returnType: CAPPluginReturnPromise),
32
- CAPPluginMethod(name: "getConnectedDevices", returnType: CAPPluginReturnPromise),
33
- CAPPluginMethod(name: "connect", returnType: CAPPluginReturnPromise),
34
- CAPPluginMethod(name: "createBond", returnType: CAPPluginReturnPromise),
35
- CAPPluginMethod(name: "isBonded", returnType: CAPPluginReturnPromise),
36
- CAPPluginMethod(name: "getBondedDevices", returnType: CAPPluginReturnPromise),
37
- CAPPluginMethod(name: "disconnect", returnType: CAPPluginReturnPromise),
38
- CAPPluginMethod(name: "getServices", returnType: CAPPluginReturnPromise),
39
- CAPPluginMethod(name: "getMtu", returnType: CAPPluginReturnPromise),
40
- CAPPluginMethod(name: "requestConnectionPriority", returnType: CAPPluginReturnPromise),
41
- CAPPluginMethod(name: "readRssi", returnType: CAPPluginReturnPromise),
42
- CAPPluginMethod(name: "read", returnType: CAPPluginReturnPromise),
43
- CAPPluginMethod(name: "write", returnType: CAPPluginReturnPromise),
44
- CAPPluginMethod(name: "writeWithoutResponse", returnType: CAPPluginReturnPromise),
45
- CAPPluginMethod(name: "readDescriptor", returnType: CAPPluginReturnPromise),
46
- CAPPluginMethod(name: "writeDescriptor", returnType: CAPPluginReturnPromise),
47
- CAPPluginMethod(name: "startNotifications", returnType: CAPPluginReturnPromise),
48
- CAPPluginMethod(name: "stopNotifications", returnType: CAPPluginReturnPromise)
49
- ]
50
- typealias BleDevice = [String: Any]
51
- typealias BleService = [String: Any]
52
- typealias BleCharacteristic = [String: Any]
53
- typealias BleDescriptor = [String: Any]
54
- private var deviceManager: DeviceManager?
55
- private var deviceMap = [String: Device]()
56
- private var displayStrings = [String: String]()
57
-
58
- override public func load() {
59
- self.displayStrings = self.getDisplayStrings()
60
- }
61
-
62
- @objc func initialize(_ call: CAPPluginCall) {
63
- self.deviceManager = DeviceManager(self.bridge?.viewController, self.displayStrings, {(success, message) in
64
- if success {
65
- call.resolve()
66
- } else {
67
- call.reject(message)
68
- }
69
- })
70
- }
71
-
72
- @objc func isEnabled(_ call: CAPPluginCall) {
73
- guard let deviceManager = self.getDeviceManager(call) else { return }
74
- let enabled: Bool = deviceManager.isEnabled()
75
- call.resolve(["value": enabled])
76
- }
77
-
78
- @objc func requestEnable(_ call: CAPPluginCall) {
79
- call.unavailable("requestEnable is not available on iOS.")
80
- }
81
-
82
- @objc func enable(_ call: CAPPluginCall) {
83
- call.unavailable("enable is not available on iOS.")
84
- }
85
-
86
- @objc func disable(_ call: CAPPluginCall) {
87
- call.unavailable("disable is not available on iOS.")
88
- }
89
-
90
- @objc func startEnabledNotifications(_ call: CAPPluginCall) {
91
- guard let deviceManager = self.getDeviceManager(call) else { return }
92
- deviceManager.registerStateReceiver({(enabled) in
93
- self.notifyListeners("onEnabledChanged", data: ["value": enabled])
94
- })
95
- call.resolve()
96
- }
97
-
98
- @objc func stopEnabledNotifications(_ call: CAPPluginCall) {
99
- guard let deviceManager = self.getDeviceManager(call) else { return }
100
- deviceManager.unregisterStateReceiver()
101
- call.resolve()
102
- }
103
-
104
- @objc func isLocationEnabled(_ call: CAPPluginCall) {
105
- call.unavailable("isLocationEnabled is not available on iOS.")
106
- }
107
-
108
- @objc func openLocationSettings(_ call: CAPPluginCall) {
109
- call.unavailable("openLocationSettings is not available on iOS.")
110
- }
111
-
112
- @objc func openBluetoothSettings(_ call: CAPPluginCall) {
113
- call.unavailable("openBluetoothSettings is not available on iOS.")
114
- }
115
-
116
- @objc func openAppSettings(_ call: CAPPluginCall) {
117
- guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
118
- call.reject("Cannot open app settings.")
119
- return
120
- }
121
-
122
- DispatchQueue.main.async {
123
- if UIApplication.shared.canOpenURL(settingsUrl) {
124
- UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
125
- call.resolve([
126
- "value": success
127
- ])
128
- })
129
- } else {
130
- call.reject("Cannot open app settings.")
131
- }
132
- }
133
- }
134
-
135
- @objc func setDisplayStrings(_ call: CAPPluginCall) {
136
- for key in ["noDeviceFound", "availableDevices", "scanning", "cancel"] {
137
- if call.getString(key) != nil {
138
- self.displayStrings[key] = call.getString(key)
139
- }
140
- }
141
- call.resolve()
142
- }
143
-
144
- @objc func requestDevice(_ call: CAPPluginCall) {
145
- guard let deviceManager = self.getDeviceManager(call) else { return }
146
- deviceManager.setDisplayStrings(self.displayStrings)
147
-
148
- let serviceUUIDs = self.getServiceUUIDs(call)
149
- let name = call.getString("name")
150
- let namePrefix = call.getString("namePrefix")
151
- let manufacturerDataFilters = self.getManufacturerDataFilters(call)
152
- let serviceDataFilters = self.getServiceDataFilters(call)
153
-
154
- let displayModeString = (call.getString("displayMode") ?? "alert").lowercased()
155
- guard ["alert", "list"].contains(displayModeString) else {
156
- call.reject("Invalid displayMode '\(call.getString("displayMode") ?? "")'. Use 'alert' or 'list'.")
157
- return
158
- }
159
- let deviceListMode: DeviceListMode = displayModeString == "list" ? .list : .alert
160
-
161
- deviceManager.startScanning(
162
- serviceUUIDs,
163
- name,
164
- namePrefix,
165
- manufacturerDataFilters,
166
- serviceDataFilters,
167
- false,
168
- deviceListMode,
169
- 30,
170
- {(success, message) in
171
- if success {
172
- guard let device = deviceManager.getDevice(message) else {
173
- call.reject("Device not found.")
174
- return
175
- }
176
- self.deviceMap[device.getId()] = device
177
- let bleDevice: BleDevice = self.getBleDevice(device)
178
- call.resolve(bleDevice)
179
- } else {
180
- call.reject(message)
181
- }
182
- },
183
- { (_, _, _) in }
184
- )
185
- }
186
-
187
- @objc func requestLEScan(_ call: CAPPluginCall) {
188
- guard let deviceManager = self.getDeviceManager(call) else { return }
189
-
190
- let serviceUUIDs = self.getServiceUUIDs(call)
191
- let name = call.getString("name")
192
- let namePrefix = call.getString("namePrefix")
193
- let allowDuplicates = call.getBool("allowDuplicates", false)
194
- let manufacturerDataFilters = self.getManufacturerDataFilters(call)
195
- let serviceDataFilters = self.getServiceDataFilters(call)
196
-
197
- deviceManager.startScanning(
198
- serviceUUIDs,
199
- name,
200
- namePrefix,
201
- manufacturerDataFilters,
202
- serviceDataFilters,
203
- allowDuplicates,
204
- .none,
205
- nil,
206
- { (success, message) in
207
- if success {
208
- call.resolve()
209
- } else {
210
- call.reject(message)
211
- }
212
- }, { (device, advertisementData, rssi) in
213
- self.deviceMap[device.getId()] = device
214
- let data = self.getScanResult(device, advertisementData, rssi)
215
- self.notifyListeners("onScanResult", data: data)
216
- }
217
- )
218
- }
219
-
220
- @objc func stopLEScan(_ call: CAPPluginCall) {
221
- guard let deviceManager = self.getDeviceManager(call) else { return }
222
- deviceManager.stopScan()
223
- call.resolve()
224
- }
225
-
226
- @objc func getDevices(_ call: CAPPluginCall) {
227
- guard let deviceManager = self.getDeviceManager(call) else { return }
228
- guard let deviceIds = call.getArray("deviceIds", String.self) else {
229
- call.reject("deviceIds must be provided")
230
- return
231
- }
232
- let deviceUUIDs: [UUID] = deviceIds.compactMap({ deviceId in
233
- return UUID(uuidString: deviceId)
234
- })
235
- let peripherals = deviceManager.getDevices(deviceUUIDs)
236
- let bleDevices: [BleDevice] = peripherals.map({peripheral in
237
- let deviceId = peripheral.identifier.uuidString
238
- guard let device = self.deviceMap[deviceId] else {
239
- let newDevice = Device(peripheral)
240
- self.deviceMap[newDevice.getId()] = newDevice
241
- return self.getBleDevice(newDevice)
242
- }
243
- return self.getBleDevice(device)
244
- })
245
- call.resolve(["devices": bleDevices])
246
- }
247
-
248
- @objc func getConnectedDevices(_ call: CAPPluginCall) {
249
- guard let deviceManager = self.getDeviceManager(call) else { return }
250
- guard let services = call.getArray("services", String.self) else {
251
- call.reject("services must be provided")
252
- return
253
- }
254
- let serviceUUIDs: [CBUUID] = services.compactMap({ service in
255
- return CBUUID(string: service)
256
- })
257
- let peripherals = deviceManager.getConnectedDevices(serviceUUIDs)
258
- let bleDevices: [BleDevice] = peripherals.map({peripheral in
259
- let deviceId = peripheral.identifier.uuidString
260
- guard let device = self.deviceMap[deviceId] else {
261
- let newDevice = Device(peripheral)
262
- self.deviceMap[newDevice.getId()] = newDevice
263
- return self.getBleDevice(newDevice)
264
- }
265
- return self.getBleDevice(device)
266
- })
267
- call.resolve(["devices": bleDevices])
268
- }
269
-
270
- @objc func connect(_ call: CAPPluginCall) {
271
- guard self.getDeviceManager(call) != nil else { return }
272
- guard let device = self.getDevice(call, checkConnection: false) else { return }
273
- let timeout = self.getTimeout(call, defaultTimeout: CONNECTION_TIMEOUT)
274
- device.setOnConnected(timeout, {(success, message) in
275
- if success {
276
- // only resolve after service discovery
277
- call.resolve()
278
- } else {
279
- call.reject(message)
280
- }
281
- })
282
- self.deviceManager?.setOnDisconnected(device, {(_, _) in
283
- let key = "disconnected|\(device.getId())"
284
- self.notifyListeners(key, data: nil)
285
- })
286
- self.deviceManager?.connect(device, timeout, {(success, message) in
287
- if success {
288
- log("Connected to peripheral. Waiting for service discovery.")
289
- } else {
290
- call.reject(message)
291
- }
292
- })
293
-
294
- }
295
-
296
- @objc func createBond(_ call: CAPPluginCall) {
297
- call.unavailable("createBond is not available on iOS.")
298
- }
299
-
300
- @objc func isBonded(_ call: CAPPluginCall) {
301
- call.unavailable("isBonded is not available on iOS.")
302
- }
303
-
304
- @objc func getBondedDevices(_ call: CAPPluginCall) {
305
- call.unavailable("getBondedDevices is not available on iOS.")
306
- }
307
-
308
- @objc func disconnect(_ call: CAPPluginCall) {
309
- guard self.getDeviceManager(call) != nil else { return }
310
- guard let device = self.getDevice(call, checkConnection: false) else { return }
311
- let timeout = self.getTimeout(call)
312
- self.deviceManager?.disconnect(device, timeout, {(success, message) in
313
- if success {
314
- call.resolve()
315
- } else {
316
- call.reject(message)
317
- }
318
- })
319
- }
320
-
321
- @objc func getServices(_ call: CAPPluginCall) {
322
- guard self.getDeviceManager(call) != nil else { return }
323
- guard let device = self.getDevice(call) else { return }
324
- let services = device.getServices()
325
- var bleServices = [BleService]()
326
- for service in services {
327
- var bleCharacteristics = [BleCharacteristic]()
328
- for characteristic in service.characteristics ?? [] {
329
- var bleDescriptors = [BleDescriptor]()
330
- for descriptor in characteristic.descriptors ?? [] {
331
- bleDescriptors.append([
332
- "uuid": cbuuidToString(descriptor.uuid)
333
- ])
334
- }
335
- bleCharacteristics.append([
336
- "uuid": cbuuidToString(characteristic.uuid),
337
- "properties": getProperties(characteristic),
338
- "descriptors": bleDescriptors
339
- ])
340
- }
341
- bleServices.append([
342
- "uuid": cbuuidToString(service.uuid),
343
- "characteristics": bleCharacteristics
344
- ])
345
- }
346
- call.resolve(["services": bleServices])
347
- }
348
-
349
- private func getProperties(_ characteristic: CBCharacteristic) -> [String: Bool] {
350
- return [
351
- "broadcast": characteristic.properties.contains(CBCharacteristicProperties.broadcast),
352
- "read": characteristic.properties.contains(CBCharacteristicProperties.read),
353
- "writeWithoutResponse": characteristic.properties.contains(CBCharacteristicProperties.writeWithoutResponse),
354
- "write": characteristic.properties.contains(CBCharacteristicProperties.write),
355
- "notify": characteristic.properties.contains(CBCharacteristicProperties.notify),
356
- "indicate": characteristic.properties.contains(CBCharacteristicProperties.indicate),
357
- "authenticatedSignedWrites": characteristic.properties.contains(CBCharacteristicProperties.authenticatedSignedWrites),
358
- "extendedProperties": characteristic.properties.contains(CBCharacteristicProperties.extendedProperties),
359
- "notifyEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.notifyEncryptionRequired),
360
- "indicateEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.indicateEncryptionRequired)
361
- ]
362
- }
363
-
364
- @objc func discoverServices(_ call: CAPPluginCall) {
365
- guard self.getDeviceManager(call) != nil else { return }
366
- guard let device = self.getDevice(call) else { return }
367
- let timeout = self.getTimeout(call)
368
- device.discoverServices(timeout, {(success, value) in
369
- if success {
370
- call.resolve()
371
- } else {
372
- call.reject(value)
373
- }
374
- })
375
- }
376
-
377
- @objc func getMtu(_ call: CAPPluginCall) {
378
- guard self.getDeviceManager(call) != nil else { return }
379
- guard let device = self.getDevice(call) else { return }
380
- call.resolve([
381
- "value": device.getMtu()
382
- ])
383
- }
384
-
385
- @objc func requestConnectionPriority(_ call: CAPPluginCall) {
386
- call.unavailable("requestConnectionPriority is not available on iOS.")
387
- }
388
-
389
- @objc func readRssi(_ call: CAPPluginCall) {
390
- guard self.getDeviceManager(call) != nil else { return }
391
- guard let device = self.getDevice(call) else { return }
392
- let timeout = self.getTimeout(call)
393
- device.readRssi(timeout, {(success, value) in
394
- if success {
395
- call.resolve([
396
- "value": value
397
- ])
398
- } else {
399
- call.reject(value)
400
- }
401
- })
402
- }
403
-
404
- @objc func read(_ call: CAPPluginCall) {
405
- guard self.getDeviceManager(call) != nil else { return }
406
- guard let device = self.getDevice(call) else { return }
407
- guard let characteristic = self.getCharacteristic(call) else { return }
408
- let timeout = self.getTimeout(call)
409
- device.read(characteristic.0, characteristic.1, timeout, {(success, value) in
410
- if success {
411
- call.resolve([
412
- "value": value
413
- ])
414
- } else {
415
- call.reject(value)
416
- }
417
- })
418
- }
419
-
420
- @objc func write(_ call: CAPPluginCall) {
421
- guard self.getDeviceManager(call) != nil else { return }
422
- guard let device = self.getDevice(call) else { return }
423
- guard let characteristic = self.getCharacteristic(call) else { return }
424
- guard let value = call.getString("value") else {
425
- call.reject("value must be provided")
426
- return
427
- }
428
- let writeType = CBCharacteristicWriteType.withResponse
429
- let timeout = self.getTimeout(call)
430
- device.write(
431
- characteristic.0,
432
- characteristic.1,
433
- value,
434
- writeType,
435
- timeout, {(success, value) in
436
- if success {
437
- call.resolve()
438
- } else {
439
- call.reject(value)
440
- }
441
- })
442
- }
443
-
444
- @objc func writeWithoutResponse(_ call: CAPPluginCall) {
445
- guard self.getDeviceManager(call) != nil else { return }
446
- guard let device = self.getDevice(call) else { return }
447
- guard let characteristic = self.getCharacteristic(call) else { return }
448
- guard let value = call.getString("value") else {
449
- call.reject("value must be provided")
450
- return
451
- }
452
- let writeType = CBCharacteristicWriteType.withoutResponse
453
- let timeout = self.getTimeout(call)
454
- device.write(
455
- characteristic.0,
456
- characteristic.1,
457
- value,
458
- writeType,
459
- timeout, {(success, value) in
460
- if success {
461
- call.resolve()
462
- } else {
463
- call.reject(value)
464
- }
465
- })
466
- }
467
-
468
- @objc func readDescriptor(_ call: CAPPluginCall) {
469
- guard self.getDeviceManager(call) != nil else { return }
470
- guard let device = self.getDevice(call) else { return }
471
- guard let descriptor = self.getDescriptor(call) else { return }
472
- let timeout = self.getTimeout(call)
473
- device.readDescriptor(
474
- descriptor.0,
475
- descriptor.1,
476
- descriptor.2,
477
- timeout, {(success, value) in
478
- if success {
479
- call.resolve([
480
- "value": value
481
- ])
482
- } else {
483
- call.reject(value)
484
- }
485
- })
486
- }
487
-
488
- @objc func writeDescriptor(_ call: CAPPluginCall) {
489
- guard self.getDeviceManager(call) != nil else { return }
490
- guard let device = self.getDevice(call) else { return }
491
- guard let descriptor = self.getDescriptor(call) else { return }
492
- guard let value = call.getString("value") else {
493
- call.reject("value must be provided")
494
- return
495
- }
496
- let timeout = self.getTimeout(call)
497
- device.writeDescriptor(
498
- descriptor.0,
499
- descriptor.1,
500
- descriptor.2,
501
- value,
502
- timeout, {(success, value) in
503
- if success {
504
- call.resolve()
505
- } else {
506
- call.reject(value)
507
- }
508
- })
509
- }
510
-
511
- @objc func startNotifications(_ call: CAPPluginCall) {
512
- guard self.getDeviceManager(call) != nil else { return }
513
- guard let device = self.getDevice(call) else { return }
514
- guard let characteristic = self.getCharacteristic(call) else { return }
515
- let timeout = self.getTimeout(call)
516
- device.setNotifications(
517
- characteristic.0,
518
- characteristic.1,
519
- true, {(_, value) in
520
- let key = "notification|\(device.getId())|\(characteristic.0.uuidString.lowercased())|\(characteristic.1.uuidString.lowercased())"
521
- self.notifyListeners(key, data: ["value": value])
522
- },
523
- timeout, {(success, value) in
524
- if success {
525
- call.resolve()
526
- } else {
527
- call.reject(value)
528
- }
529
- })
530
- }
531
-
532
- @objc func stopNotifications(_ call: CAPPluginCall) {
533
- guard self.getDeviceManager(call) != nil else { return }
534
- guard let device = self.getDevice(call) else { return }
535
- guard let characteristic = self.getCharacteristic(call) else { return }
536
- let timeout = self.getTimeout(call)
537
- device.setNotifications(
538
- characteristic.0,
539
- characteristic.1,
540
- false,
541
- nil,
542
- timeout, {(success, value) in
543
- if success {
544
- call.resolve()
545
- } else {
546
- call.reject(value)
547
- }
548
- })
549
- }
550
-
551
- private func getDisplayStrings() -> [String: String] {
552
- let configDisplayStrings = getConfig().getObject("displayStrings") as? [String: String] ?? [String: String]()
553
- var displayStrings = [String: String]()
554
- displayStrings["noDeviceFound"] = configDisplayStrings["noDeviceFound"] ?? "No device found"
555
- displayStrings["availableDevices"] = configDisplayStrings["availableDevices"] ?? "Available devices"
556
- displayStrings["scanning"] = configDisplayStrings["scanning"] ?? "Scanning..."
557
- displayStrings["cancel"] = configDisplayStrings["cancel"] ?? "Cancel"
558
- return displayStrings
559
- }
560
-
561
- private func getDeviceManager(_ call: CAPPluginCall) -> DeviceManager? {
562
- guard let deviceManager = self.deviceManager else {
563
- call.reject("Bluetooth LE not initialized.")
564
- return nil
565
- }
566
- return deviceManager
567
- }
568
-
569
- private func getServiceUUIDs(_ call: CAPPluginCall) -> [CBUUID] {
570
- let services = call.getArray("services", String.self) ?? []
571
- let serviceUUIDs = services.map({(service) -> CBUUID in
572
- return CBUUID(string: service)
573
- })
574
- return serviceUUIDs
575
- }
576
-
577
- private func getManufacturerDataFilters(_ call: CAPPluginCall) -> [ManufacturerDataFilter]? {
578
- guard let manufacturerDataArray = call.getArray("manufacturerData") else {
579
- return nil
580
- }
581
-
582
- var manufacturerDataFilters: [ManufacturerDataFilter] = []
583
-
584
- for index in 0..<manufacturerDataArray.count {
585
- guard let dataObject = manufacturerDataArray[index] as? JSObject,
586
- let companyIdentifier = dataObject["companyIdentifier"] as? UInt16 else {
587
- // Invalid or missing company identifier
588
- return nil
589
- }
590
-
591
- let dataPrefix: Data? = {
592
- guard let prefixString = dataObject["dataPrefix"] as? String else { return nil }
593
- return stringToData(prefixString)
594
- }()
595
-
596
- let mask: Data? = {
597
- guard let maskString = dataObject["mask"] as? String else { return nil }
598
- return stringToData(maskString)
599
- }()
600
-
601
- let manufacturerFilter = ManufacturerDataFilter(
602
- companyIdentifier: companyIdentifier,
603
- dataPrefix: dataPrefix,
604
- mask: mask
605
- )
606
-
607
- manufacturerDataFilters.append(manufacturerFilter)
608
- }
609
-
610
- return manufacturerDataFilters
611
- }
612
-
613
- private func getServiceDataFilters(_ call: CAPPluginCall) -> [ServiceDataFilter]? {
614
- guard let serviceDataArray = call.getArray("serviceData") else {
615
- return nil
616
- }
617
-
618
- var serviceDataFilters: [ServiceDataFilter] = []
619
-
620
- for index in 0..<serviceDataArray.count {
621
- guard let dataObject = serviceDataArray[index] as? JSObject,
622
- let serviceUuidString = dataObject["serviceUuid"] as? String else {
623
- // Invalid or missing service UUID
624
- return nil
625
- }
626
-
627
- let serviceUuid = CBUUID(string: serviceUuidString)
628
-
629
- let dataPrefix: Data? = {
630
- guard let prefixString = dataObject["dataPrefix"] as? String else { return nil }
631
- return stringToData(prefixString)
632
- }()
633
-
634
- let mask: Data? = {
635
- guard let maskString = dataObject["mask"] as? String else { return nil }
636
- return stringToData(maskString)
637
- }()
638
-
639
- let serviceDataFilter = ServiceDataFilter(
640
- serviceUuid: serviceUuid,
641
- dataPrefix: dataPrefix,
642
- mask: mask
643
- )
644
-
645
- serviceDataFilters.append(serviceDataFilter)
646
- }
647
-
648
- return serviceDataFilters
649
- }
650
-
651
- private func getDevice(_ call: CAPPluginCall, checkConnection: Bool = true) -> Device? {
652
- guard let deviceId = call.getString("deviceId") else {
653
- call.reject("deviceId required.")
654
- return nil
655
- }
656
- guard let device = self.deviceMap[deviceId] else {
657
- call.reject("Device not found. Call 'requestDevice', 'requestLEScan' or 'getDevices' first.")
658
- return nil
659
- }
660
- if checkConnection {
661
- guard device.isConnected() else {
662
- call.reject("Not connected to device.")
663
- return nil
664
- }
665
- }
666
- return device
667
- }
668
-
669
- private func getTimeout(_ call: CAPPluginCall, defaultTimeout: Double = DEFAULT_TIMEOUT) -> Double {
670
- guard let timeout = call.getDouble("timeout") else {
671
- return defaultTimeout
672
- }
673
- return timeout / 1000
674
- }
675
-
676
- private func getCharacteristic(_ call: CAPPluginCall) -> (CBUUID, CBUUID)? {
677
- guard let service = call.getString("service") else {
678
- call.reject("Service UUID required.")
679
- return nil
680
- }
681
- let serviceUUID = CBUUID(string: service)
682
-
683
- guard let characteristic = call.getString("characteristic") else {
684
- call.reject("Characteristic UUID required.")
685
- return nil
686
- }
687
- let characteristicUUID = CBUUID(string: characteristic)
688
- return (serviceUUID, characteristicUUID)
689
- }
690
-
691
- private func getDescriptor(_ call: CAPPluginCall) -> (CBUUID, CBUUID, CBUUID)? {
692
- guard let characteristic = getCharacteristic(call) else {
693
- return nil
694
- }
695
- guard let descriptor = call.getString("descriptor") else {
696
- call.reject("Descriptor UUID required.")
697
- return nil
698
- }
699
- let descriptorUUID = CBUUID(string: descriptor)
700
-
701
- return (characteristic.0, characteristic.1, descriptorUUID)
702
- }
703
-
704
- private func getBleDevice(_ device: Device) -> BleDevice {
705
- var bleDevice = [
706
- "deviceId": device.getId()
707
- ]
708
- if device.getName() != nil {
709
- bleDevice["name"] = device.getName()
710
- }
711
- return bleDevice
712
- }
713
-
714
- private func getScanResult(_ device: Device, _ advertisementData: [String: Any], _ rssi: NSNumber) -> [String: Any] {
715
- var data = [
716
- "device": self.getBleDevice(device),
717
- "rssi": rssi,
718
- "txPower": advertisementData[CBAdvertisementDataTxPowerLevelKey] ?? 127,
719
- "uuids": (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? []).map({(uuid) -> String in
720
- return cbuuidToString(uuid)
721
- })
722
- ]
723
-
724
- let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String
725
- if localName != nil {
726
- data["localName"] = localName
727
- }
728
-
729
- let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
730
- if manufacturerData != nil {
731
- data["manufacturerData"] = self.getManufacturerData(data: manufacturerData!)
732
- }
733
-
734
- let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
735
- if serviceData != nil {
736
- data["serviceData"] = self.getServiceData(data: serviceData!)
737
- }
738
- return data
739
- }
740
-
741
- private func getManufacturerData(data: Data) -> [String: String] {
742
- var company = 0
743
- var rest = ""
744
- for (index, byte) in data.enumerated() {
745
- if index == 0 {
746
- company += Int(byte)
747
- } else if index == 1 {
748
- company += Int(byte) * 256
749
- } else {
750
- rest += String(format: "%02hhx ", byte)
751
- }
752
- }
753
- return [String(company): rest]
754
- }
755
-
756
- private func getServiceData(data: [CBUUID: Data]) -> [String: String] {
757
- var result: [String: String] = [:]
758
- for (key, value) in data {
759
- result[cbuuidToString(key)] = dataToString(value)
760
- }
761
- return result
762
- }
763
- }
1
+ // swiftlint:disable identifier_name
2
+ // swiftlint:disable type_body_length
3
+ import Foundation
4
+ import Capacitor
5
+ import CoreBluetooth
6
+
7
+ let CONNECTION_TIMEOUT: Double = 10
8
+ let DEFAULT_TIMEOUT: Double = 5
9
+
10
+ @objc(BluetoothLe)
11
+ public class BluetoothLe: CAPPlugin, CAPBridgedPlugin {
12
+ public let identifier = "BluetoothLe"
13
+ public let jsName = "BluetoothLe"
14
+ public let pluginMethods: [CAPPluginMethod] = [
15
+ CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise),
16
+ CAPPluginMethod(name: "isEnabled", returnType: CAPPluginReturnPromise),
17
+ CAPPluginMethod(name: "requestEnable", returnType: CAPPluginReturnPromise),
18
+ CAPPluginMethod(name: "enable", returnType: CAPPluginReturnPromise),
19
+ CAPPluginMethod(name: "disable", returnType: CAPPluginReturnPromise),
20
+ CAPPluginMethod(name: "startEnabledNotifications", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "stopEnabledNotifications", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "isLocationEnabled", returnType: CAPPluginReturnPromise),
23
+ CAPPluginMethod(name: "openLocationSettings", returnType: CAPPluginReturnPromise),
24
+ CAPPluginMethod(name: "openBluetoothSettings", returnType: CAPPluginReturnPromise),
25
+ CAPPluginMethod(name: "openAppSettings", returnType: CAPPluginReturnPromise),
26
+ CAPPluginMethod(name: "setDisplayStrings", returnType: CAPPluginReturnPromise),
27
+ CAPPluginMethod(name: "requestDevice", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "requestLEScan", returnType: CAPPluginReturnPromise),
29
+ CAPPluginMethod(name: "stopLEScan", returnType: CAPPluginReturnPromise),
30
+ CAPPluginMethod(name: "getDevices", returnType: CAPPluginReturnPromise),
31
+ CAPPluginMethod(name: "discoverServices", returnType: CAPPluginReturnPromise),
32
+ CAPPluginMethod(name: "getConnectedDevices", returnType: CAPPluginReturnPromise),
33
+ CAPPluginMethod(name: "connect", returnType: CAPPluginReturnPromise),
34
+ CAPPluginMethod(name: "createBond", returnType: CAPPluginReturnPromise),
35
+ CAPPluginMethod(name: "isBonded", returnType: CAPPluginReturnPromise),
36
+ CAPPluginMethod(name: "getBondedDevices", returnType: CAPPluginReturnPromise),
37
+ CAPPluginMethod(name: "disconnect", returnType: CAPPluginReturnPromise),
38
+ CAPPluginMethod(name: "getServices", returnType: CAPPluginReturnPromise),
39
+ CAPPluginMethod(name: "getMtu", returnType: CAPPluginReturnPromise),
40
+ CAPPluginMethod(name: "requestConnectionPriority", returnType: CAPPluginReturnPromise),
41
+ CAPPluginMethod(name: "readRssi", returnType: CAPPluginReturnPromise),
42
+ CAPPluginMethod(name: "read", returnType: CAPPluginReturnPromise),
43
+ CAPPluginMethod(name: "write", returnType: CAPPluginReturnPromise),
44
+ CAPPluginMethod(name: "writeWithoutResponse", returnType: CAPPluginReturnPromise),
45
+ CAPPluginMethod(name: "readDescriptor", returnType: CAPPluginReturnPromise),
46
+ CAPPluginMethod(name: "writeDescriptor", returnType: CAPPluginReturnPromise),
47
+ CAPPluginMethod(name: "startNotifications", returnType: CAPPluginReturnPromise),
48
+ CAPPluginMethod(name: "stopNotifications", returnType: CAPPluginReturnPromise)
49
+ ]
50
+ typealias BleDevice = [String: Any]
51
+ typealias BleService = [String: Any]
52
+ typealias BleCharacteristic = [String: Any]
53
+ typealias BleDescriptor = [String: Any]
54
+ private var deviceManager: DeviceManager?
55
+ private let deviceMap = ThreadSafeDictionary<String, Device>()
56
+ private var displayStrings = [String: String]()
57
+
58
+ override public func load() {
59
+ self.displayStrings = self.getDisplayStrings()
60
+ }
61
+
62
+ @objc func initialize(_ call: CAPPluginCall) {
63
+ self.deviceManager = DeviceManager(self.bridge?.viewController, self.displayStrings, {(success, message) in
64
+ if success {
65
+ call.resolve()
66
+ } else {
67
+ call.reject(message)
68
+ }
69
+ })
70
+ }
71
+
72
+ @objc func isEnabled(_ call: CAPPluginCall) {
73
+ guard let deviceManager = self.getDeviceManager(call) else { return }
74
+ let enabled: Bool = deviceManager.isEnabled()
75
+ call.resolve(["value": enabled])
76
+ }
77
+
78
+ @objc func requestEnable(_ call: CAPPluginCall) {
79
+ call.unavailable("requestEnable is not available on iOS.")
80
+ }
81
+
82
+ @objc func enable(_ call: CAPPluginCall) {
83
+ call.unavailable("enable is not available on iOS.")
84
+ }
85
+
86
+ @objc func disable(_ call: CAPPluginCall) {
87
+ call.unavailable("disable is not available on iOS.")
88
+ }
89
+
90
+ @objc func startEnabledNotifications(_ call: CAPPluginCall) {
91
+ guard let deviceManager = self.getDeviceManager(call) else { return }
92
+ deviceManager.registerStateReceiver({(enabled) in
93
+ self.notifyListeners("onEnabledChanged", data: ["value": enabled])
94
+ })
95
+ call.resolve()
96
+ }
97
+
98
+ @objc func stopEnabledNotifications(_ call: CAPPluginCall) {
99
+ guard let deviceManager = self.getDeviceManager(call) else { return }
100
+ deviceManager.unregisterStateReceiver()
101
+ call.resolve()
102
+ }
103
+
104
+ @objc func isLocationEnabled(_ call: CAPPluginCall) {
105
+ call.unavailable("isLocationEnabled is not available on iOS.")
106
+ }
107
+
108
+ @objc func openLocationSettings(_ call: CAPPluginCall) {
109
+ call.unavailable("openLocationSettings is not available on iOS.")
110
+ }
111
+
112
+ @objc func openBluetoothSettings(_ call: CAPPluginCall) {
113
+ call.unavailable("openBluetoothSettings is not available on iOS.")
114
+ }
115
+
116
+ @objc func openAppSettings(_ call: CAPPluginCall) {
117
+ guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
118
+ call.reject("Cannot open app settings.")
119
+ return
120
+ }
121
+
122
+ DispatchQueue.main.async {
123
+ if UIApplication.shared.canOpenURL(settingsUrl) {
124
+ UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
125
+ call.resolve([
126
+ "value": success
127
+ ])
128
+ })
129
+ } else {
130
+ call.reject("Cannot open app settings.")
131
+ }
132
+ }
133
+ }
134
+
135
+ @objc func setDisplayStrings(_ call: CAPPluginCall) {
136
+ for key in ["noDeviceFound", "availableDevices", "scanning", "cancel"] {
137
+ if let value = call.getString(key) {
138
+ self.displayStrings[key] = value
139
+ }
140
+ }
141
+ call.resolve()
142
+ }
143
+
144
+ @objc func requestDevice(_ call: CAPPluginCall) {
145
+ guard let deviceManager = self.getDeviceManager(call) else { return }
146
+ deviceManager.setDisplayStrings(self.displayStrings)
147
+
148
+ let serviceUUIDs = self.getServiceUUIDs(call)
149
+ let name = call.getString("name")
150
+ let namePrefix = call.getString("namePrefix")
151
+ let manufacturerDataFilters = self.getManufacturerDataFilters(call)
152
+ let serviceDataFilters = self.getServiceDataFilters(call)
153
+
154
+ let displayModeString = (call.getString("displayMode") ?? "alert").lowercased()
155
+ guard ["alert", "list"].contains(displayModeString) else {
156
+ call.reject("Invalid displayMode '\(call.getString("displayMode") ?? "")'. Use 'alert' or 'list'.")
157
+ return
158
+ }
159
+ let deviceListMode: DeviceListMode = displayModeString == "list" ? .list : .alert
160
+
161
+ deviceManager.startScanning(
162
+ serviceUUIDs,
163
+ name,
164
+ namePrefix,
165
+ manufacturerDataFilters,
166
+ serviceDataFilters,
167
+ false,
168
+ deviceListMode,
169
+ 30,
170
+ {(success, message) in
171
+ if success {
172
+ guard let device = deviceManager.getDevice(message) else {
173
+ call.reject("Device not found.")
174
+ return
175
+ }
176
+ let storedDevice = self.deviceMap.getOrInsert(
177
+ key: device.getId(),
178
+ create: { device },
179
+ update: { $0.updatePeripheral(device.getPeripheral()) }
180
+ ).value
181
+ let bleDevice: BleDevice = self.getBleDevice(storedDevice)
182
+ call.resolve(bleDevice)
183
+ } else {
184
+ call.reject(message)
185
+ }
186
+ },
187
+ { (_, _, _) in }
188
+ )
189
+ }
190
+
191
+ @objc func requestLEScan(_ call: CAPPluginCall) {
192
+ guard let deviceManager = self.getDeviceManager(call) else { return }
193
+
194
+ let serviceUUIDs = self.getServiceUUIDs(call)
195
+ let name = call.getString("name")
196
+ let namePrefix = call.getString("namePrefix")
197
+ let allowDuplicates = call.getBool("allowDuplicates", false)
198
+ let manufacturerDataFilters = self.getManufacturerDataFilters(call)
199
+ let serviceDataFilters = self.getServiceDataFilters(call)
200
+
201
+ deviceManager.startScanning(
202
+ serviceUUIDs,
203
+ name,
204
+ namePrefix,
205
+ manufacturerDataFilters,
206
+ serviceDataFilters,
207
+ allowDuplicates,
208
+ .none,
209
+ nil,
210
+ { (success, message) in
211
+ if success {
212
+ call.resolve()
213
+ } else {
214
+ call.reject(message)
215
+ }
216
+ }, { (device, advertisementData, rssi) in
217
+ let storedDevice = self.deviceMap.getOrInsert(
218
+ key: device.getId(),
219
+ create: { device },
220
+ update: { $0.updatePeripheral(device.getPeripheral()) }
221
+ ).value
222
+ let data = self.getScanResult(storedDevice, advertisementData, rssi)
223
+ self.notifyListeners("onScanResult", data: data)
224
+ }
225
+ )
226
+ }
227
+
228
+ @objc func stopLEScan(_ call: CAPPluginCall) {
229
+ guard let deviceManager = self.getDeviceManager(call) else { return }
230
+ deviceManager.stopScan()
231
+ call.resolve()
232
+ }
233
+
234
+ @objc func getDevices(_ call: CAPPluginCall) {
235
+ guard let deviceManager = self.getDeviceManager(call) else { return }
236
+ guard let deviceIds = call.getArray("deviceIds", String.self) else {
237
+ call.reject("deviceIds must be provided")
238
+ return
239
+ }
240
+ let deviceUUIDs: [UUID] = deviceIds.compactMap({ deviceId in
241
+ return UUID(uuidString: deviceId)
242
+ })
243
+ let peripherals = deviceManager.getDevices(deviceUUIDs)
244
+ let bleDevices: [BleDevice] = peripherals.map({peripheral in
245
+ let deviceId = peripheral.identifier.uuidString
246
+ let device = self.deviceMap.getOrInsert(
247
+ key: deviceId,
248
+ create: { Device(peripheral) },
249
+ update: { $0.updatePeripheral(peripheral) }
250
+ ).value
251
+ return self.getBleDevice(device)
252
+ })
253
+ call.resolve(["devices": bleDevices])
254
+ }
255
+
256
+ @objc func getConnectedDevices(_ call: CAPPluginCall) {
257
+ guard let deviceManager = self.getDeviceManager(call) else { return }
258
+ guard let services = call.getArray("services", String.self) else {
259
+ call.reject("services must be provided")
260
+ return
261
+ }
262
+ let serviceUUIDs: [CBUUID] = services.compactMap({ service in
263
+ return CBUUID(string: service)
264
+ })
265
+ let peripherals = deviceManager.getConnectedDevices(serviceUUIDs)
266
+ let bleDevices: [BleDevice] = peripherals.map({peripheral in
267
+ let deviceId = peripheral.identifier.uuidString
268
+ let device = self.deviceMap.getOrInsert(
269
+ key: deviceId,
270
+ create: { Device(peripheral) },
271
+ update: { $0.updatePeripheral(peripheral) }
272
+ ).value
273
+ return self.getBleDevice(device)
274
+ })
275
+ call.resolve(["devices": bleDevices])
276
+ }
277
+
278
+ @objc func connect(_ call: CAPPluginCall) {
279
+ guard self.getDeviceManager(call) != nil else { return }
280
+ guard let device = self.getDevice(call, checkConnection: false) else { return }
281
+ let timeout = self.getTimeout(call, defaultTimeout: CONNECTION_TIMEOUT)
282
+ device.setOnConnected(timeout, {(success, message) in
283
+ if success {
284
+ // only resolve after service discovery
285
+ call.resolve()
286
+ } else {
287
+ call.reject(message)
288
+ }
289
+ })
290
+ self.deviceManager?.setOnDisconnected(device, {(_, _) in
291
+ let key = "disconnected|\(device.getId())"
292
+ self.notifyListeners(key, data: nil)
293
+ })
294
+ self.deviceManager?.connect(device, timeout, {(success, message) in
295
+ if success {
296
+ log("Connected to peripheral. Waiting for service discovery.")
297
+ } else {
298
+ call.reject(message)
299
+ }
300
+ })
301
+
302
+ }
303
+
304
+ @objc func createBond(_ call: CAPPluginCall) {
305
+ call.unavailable("createBond is not available on iOS.")
306
+ }
307
+
308
+ @objc func isBonded(_ call: CAPPluginCall) {
309
+ call.unavailable("isBonded is not available on iOS.")
310
+ }
311
+
312
+ @objc func getBondedDevices(_ call: CAPPluginCall) {
313
+ call.unavailable("getBondedDevices is not available on iOS.")
314
+ }
315
+
316
+ @objc func disconnect(_ call: CAPPluginCall) {
317
+ guard self.getDeviceManager(call) != nil else { return }
318
+ guard let device = self.getDevice(call, checkConnection: false) else { return }
319
+ let timeout = self.getTimeout(call)
320
+ self.deviceManager?.disconnect(device, timeout, {(success, message) in
321
+ if success {
322
+ call.resolve()
323
+ } else {
324
+ call.reject(message)
325
+ }
326
+ })
327
+ }
328
+
329
+ @objc func getServices(_ call: CAPPluginCall) {
330
+ guard self.getDeviceManager(call) != nil else { return }
331
+ guard let device = self.getDevice(call) else { return }
332
+ let services = device.getServices()
333
+ var bleServices = [BleService]()
334
+ for service in services {
335
+ var bleCharacteristics = [BleCharacteristic]()
336
+ for characteristic in service.characteristics ?? [] {
337
+ var bleDescriptors = [BleDescriptor]()
338
+ for descriptor in characteristic.descriptors ?? [] {
339
+ bleDescriptors.append([
340
+ "uuid": cbuuidToString(descriptor.uuid)
341
+ ])
342
+ }
343
+ bleCharacteristics.append([
344
+ "uuid": cbuuidToString(characteristic.uuid),
345
+ "properties": getProperties(characteristic),
346
+ "descriptors": bleDescriptors
347
+ ])
348
+ }
349
+ bleServices.append([
350
+ "uuid": cbuuidToString(service.uuid),
351
+ "characteristics": bleCharacteristics
352
+ ])
353
+ }
354
+ call.resolve(["services": bleServices])
355
+ }
356
+
357
+ private func getProperties(_ characteristic: CBCharacteristic) -> [String: Bool] {
358
+ return [
359
+ "broadcast": characteristic.properties.contains(CBCharacteristicProperties.broadcast),
360
+ "read": characteristic.properties.contains(CBCharacteristicProperties.read),
361
+ "writeWithoutResponse": characteristic.properties.contains(CBCharacteristicProperties.writeWithoutResponse),
362
+ "write": characteristic.properties.contains(CBCharacteristicProperties.write),
363
+ "notify": characteristic.properties.contains(CBCharacteristicProperties.notify),
364
+ "indicate": characteristic.properties.contains(CBCharacteristicProperties.indicate),
365
+ "authenticatedSignedWrites": characteristic.properties.contains(CBCharacteristicProperties.authenticatedSignedWrites),
366
+ "extendedProperties": characteristic.properties.contains(CBCharacteristicProperties.extendedProperties),
367
+ "notifyEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.notifyEncryptionRequired),
368
+ "indicateEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.indicateEncryptionRequired)
369
+ ]
370
+ }
371
+
372
+ @objc func discoverServices(_ call: CAPPluginCall) {
373
+ guard self.getDeviceManager(call) != nil else { return }
374
+ guard let device = self.getDevice(call) else { return }
375
+ let timeout = self.getTimeout(call)
376
+ device.discoverServices(timeout, {(success, value) in
377
+ if success {
378
+ call.resolve()
379
+ } else {
380
+ call.reject(value)
381
+ }
382
+ })
383
+ }
384
+
385
+ @objc func getMtu(_ call: CAPPluginCall) {
386
+ guard self.getDeviceManager(call) != nil else { return }
387
+ guard let device = self.getDevice(call) else { return }
388
+ call.resolve([
389
+ "value": device.getMtu()
390
+ ])
391
+ }
392
+
393
+ @objc func requestConnectionPriority(_ call: CAPPluginCall) {
394
+ call.unavailable("requestConnectionPriority is not available on iOS.")
395
+ }
396
+
397
+ @objc func readRssi(_ call: CAPPluginCall) {
398
+ guard self.getDeviceManager(call) != nil else { return }
399
+ guard let device = self.getDevice(call) else { return }
400
+ let timeout = self.getTimeout(call)
401
+ device.readRssi(timeout, {(success, value) in
402
+ if success {
403
+ call.resolve([
404
+ "value": value
405
+ ])
406
+ } else {
407
+ call.reject(value)
408
+ }
409
+ })
410
+ }
411
+
412
+ @objc func read(_ call: CAPPluginCall) {
413
+ guard self.getDeviceManager(call) != nil else { return }
414
+ guard let device = self.getDevice(call) else { return }
415
+ guard let characteristic = self.getCharacteristic(call) else { return }
416
+ let timeout = self.getTimeout(call)
417
+ device.read(characteristic.0, characteristic.1, timeout, {(success, value) in
418
+ if success {
419
+ call.resolve([
420
+ "value": value
421
+ ])
422
+ } else {
423
+ call.reject(value)
424
+ }
425
+ })
426
+ }
427
+
428
+ @objc func write(_ call: CAPPluginCall) {
429
+ guard self.getDeviceManager(call) != nil else { return }
430
+ guard let device = self.getDevice(call) else { return }
431
+ guard let characteristic = self.getCharacteristic(call) else { return }
432
+ guard let value = call.getString("value") else {
433
+ call.reject("value must be provided")
434
+ return
435
+ }
436
+ let writeType = CBCharacteristicWriteType.withResponse
437
+ let timeout = self.getTimeout(call)
438
+ device.write(
439
+ characteristic.0,
440
+ characteristic.1,
441
+ value,
442
+ writeType,
443
+ timeout, {(success, value) in
444
+ if success {
445
+ call.resolve()
446
+ } else {
447
+ call.reject(value)
448
+ }
449
+ })
450
+ }
451
+
452
+ @objc func writeWithoutResponse(_ call: CAPPluginCall) {
453
+ guard self.getDeviceManager(call) != nil else { return }
454
+ guard let device = self.getDevice(call) else { return }
455
+ guard let characteristic = self.getCharacteristic(call) else { return }
456
+ guard let value = call.getString("value") else {
457
+ call.reject("value must be provided")
458
+ return
459
+ }
460
+ let writeType = CBCharacteristicWriteType.withoutResponse
461
+ let timeout = self.getTimeout(call)
462
+ device.write(
463
+ characteristic.0,
464
+ characteristic.1,
465
+ value,
466
+ writeType,
467
+ timeout, {(success, value) in
468
+ if success {
469
+ call.resolve()
470
+ } else {
471
+ call.reject(value)
472
+ }
473
+ })
474
+ }
475
+
476
+ @objc func readDescriptor(_ call: CAPPluginCall) {
477
+ guard self.getDeviceManager(call) != nil else { return }
478
+ guard let device = self.getDevice(call) else { return }
479
+ guard let descriptor = self.getDescriptor(call) else { return }
480
+ let timeout = self.getTimeout(call)
481
+ device.readDescriptor(
482
+ descriptor.0,
483
+ descriptor.1,
484
+ descriptor.2,
485
+ timeout, {(success, value) in
486
+ if success {
487
+ call.resolve([
488
+ "value": value
489
+ ])
490
+ } else {
491
+ call.reject(value)
492
+ }
493
+ })
494
+ }
495
+
496
+ @objc func writeDescriptor(_ call: CAPPluginCall) {
497
+ guard self.getDeviceManager(call) != nil else { return }
498
+ guard let device = self.getDevice(call) else { return }
499
+ guard let descriptor = self.getDescriptor(call) else { return }
500
+ guard let value = call.getString("value") else {
501
+ call.reject("value must be provided")
502
+ return
503
+ }
504
+ let timeout = self.getTimeout(call)
505
+ device.writeDescriptor(
506
+ descriptor.0,
507
+ descriptor.1,
508
+ descriptor.2,
509
+ value,
510
+ timeout, {(success, value) in
511
+ if success {
512
+ call.resolve()
513
+ } else {
514
+ call.reject(value)
515
+ }
516
+ })
517
+ }
518
+
519
+ @objc func startNotifications(_ call: CAPPluginCall) {
520
+ guard self.getDeviceManager(call) != nil else { return }
521
+ guard let device = self.getDevice(call) else { return }
522
+ guard let characteristic = self.getCharacteristic(call) else { return }
523
+ let timeout = self.getTimeout(call)
524
+ device.setNotifications(
525
+ characteristic.0,
526
+ characteristic.1,
527
+ true, {(_, value) in
528
+ let key = "notification|\(device.getId())|\(characteristic.0.uuidString.lowercased())|\(characteristic.1.uuidString.lowercased())"
529
+ self.notifyListeners(key, data: ["value": value])
530
+ },
531
+ timeout, {(success, value) in
532
+ if success {
533
+ call.resolve()
534
+ } else {
535
+ call.reject(value)
536
+ }
537
+ })
538
+ }
539
+
540
+ @objc func stopNotifications(_ call: CAPPluginCall) {
541
+ guard self.getDeviceManager(call) != nil else { return }
542
+ guard let device = self.getDevice(call) else { return }
543
+ guard let characteristic = self.getCharacteristic(call) else { return }
544
+ let timeout = self.getTimeout(call)
545
+ device.setNotifications(
546
+ characteristic.0,
547
+ characteristic.1,
548
+ false,
549
+ nil,
550
+ timeout, {(success, value) in
551
+ if success {
552
+ call.resolve()
553
+ } else {
554
+ call.reject(value)
555
+ }
556
+ })
557
+ }
558
+
559
+ private func getDisplayStrings() -> [String: String] {
560
+ let configDisplayStrings = getConfig().getObject("displayStrings") as? [String: String] ?? [String: String]()
561
+ var displayStrings = [String: String]()
562
+ displayStrings["noDeviceFound"] = configDisplayStrings["noDeviceFound"] ?? "No device found"
563
+ displayStrings["availableDevices"] = configDisplayStrings["availableDevices"] ?? "Available devices"
564
+ displayStrings["scanning"] = configDisplayStrings["scanning"] ?? "Scanning..."
565
+ displayStrings["cancel"] = configDisplayStrings["cancel"] ?? "Cancel"
566
+ return displayStrings
567
+ }
568
+
569
+ private func getDeviceManager(_ call: CAPPluginCall) -> DeviceManager? {
570
+ guard let deviceManager = self.deviceManager else {
571
+ call.reject("Bluetooth LE not initialized.")
572
+ return nil
573
+ }
574
+ return deviceManager
575
+ }
576
+
577
+ private func getServiceUUIDs(_ call: CAPPluginCall) -> [CBUUID] {
578
+ let services = call.getArray("services", String.self) ?? []
579
+ let serviceUUIDs = services.map({(service) -> CBUUID in
580
+ return CBUUID(string: service)
581
+ })
582
+ return serviceUUIDs
583
+ }
584
+
585
+ private func getManufacturerDataFilters(_ call: CAPPluginCall) -> [ManufacturerDataFilter]? {
586
+ guard let manufacturerDataArray = call.getArray("manufacturerData") else {
587
+ return nil
588
+ }
589
+
590
+ var manufacturerDataFilters: [ManufacturerDataFilter] = []
591
+
592
+ for index in 0..<manufacturerDataArray.count {
593
+ guard let dataObject = manufacturerDataArray[index] as? JSObject,
594
+ let companyIdentifier = dataObject["companyIdentifier"] as? UInt16 else {
595
+ // Invalid or missing company identifier
596
+ return nil
597
+ }
598
+
599
+ let dataPrefix: Data? = {
600
+ guard let prefixString = dataObject["dataPrefix"] as? String else { return nil }
601
+ return stringToData(prefixString)
602
+ }()
603
+
604
+ let mask: Data? = {
605
+ guard let maskString = dataObject["mask"] as? String else { return nil }
606
+ return stringToData(maskString)
607
+ }()
608
+
609
+ let manufacturerFilter = ManufacturerDataFilter(
610
+ companyIdentifier: companyIdentifier,
611
+ dataPrefix: dataPrefix,
612
+ mask: mask
613
+ )
614
+
615
+ manufacturerDataFilters.append(manufacturerFilter)
616
+ }
617
+
618
+ return manufacturerDataFilters
619
+ }
620
+
621
+ private func getServiceDataFilters(_ call: CAPPluginCall) -> [ServiceDataFilter]? {
622
+ guard let serviceDataArray = call.getArray("serviceData") else {
623
+ return nil
624
+ }
625
+
626
+ var serviceDataFilters: [ServiceDataFilter] = []
627
+
628
+ for index in 0..<serviceDataArray.count {
629
+ guard let dataObject = serviceDataArray[index] as? JSObject,
630
+ let serviceUuidString = dataObject["serviceUuid"] as? String else {
631
+ // Invalid or missing service UUID
632
+ return nil
633
+ }
634
+
635
+ let serviceUuid = CBUUID(string: serviceUuidString)
636
+
637
+ let dataPrefix: Data? = {
638
+ guard let prefixString = dataObject["dataPrefix"] as? String else { return nil }
639
+ return stringToData(prefixString)
640
+ }()
641
+
642
+ let mask: Data? = {
643
+ guard let maskString = dataObject["mask"] as? String else { return nil }
644
+ return stringToData(maskString)
645
+ }()
646
+
647
+ let serviceDataFilter = ServiceDataFilter(
648
+ serviceUuid: serviceUuid,
649
+ dataPrefix: dataPrefix,
650
+ mask: mask
651
+ )
652
+
653
+ serviceDataFilters.append(serviceDataFilter)
654
+ }
655
+
656
+ return serviceDataFilters
657
+ }
658
+
659
+ private func getDevice(_ call: CAPPluginCall, checkConnection: Bool = true) -> Device? {
660
+ guard let deviceId = call.getString("deviceId") else {
661
+ call.reject("deviceId required.")
662
+ return nil
663
+ }
664
+ guard let device = self.deviceMap[deviceId] else {
665
+ call.reject("Device not found. Call 'requestDevice', 'requestLEScan' or 'getDevices' first.")
666
+ return nil
667
+ }
668
+ if checkConnection {
669
+ guard device.isConnected() else {
670
+ call.reject("Not connected to device.")
671
+ return nil
672
+ }
673
+ }
674
+ return device
675
+ }
676
+
677
+ private func getTimeout(_ call: CAPPluginCall, defaultTimeout: Double = DEFAULT_TIMEOUT) -> Double {
678
+ guard let timeout = call.getDouble("timeout") else {
679
+ return defaultTimeout
680
+ }
681
+ return timeout / 1000
682
+ }
683
+
684
+ private func getCharacteristic(_ call: CAPPluginCall) -> (CBUUID, CBUUID)? {
685
+ guard let service = call.getString("service") else {
686
+ call.reject("Service UUID required.")
687
+ return nil
688
+ }
689
+ let serviceUUID = CBUUID(string: service)
690
+
691
+ guard let characteristic = call.getString("characteristic") else {
692
+ call.reject("Characteristic UUID required.")
693
+ return nil
694
+ }
695
+ let characteristicUUID = CBUUID(string: characteristic)
696
+ return (serviceUUID, characteristicUUID)
697
+ }
698
+
699
+ private func getDescriptor(_ call: CAPPluginCall) -> (CBUUID, CBUUID, CBUUID)? {
700
+ guard let characteristic = getCharacteristic(call) else {
701
+ return nil
702
+ }
703
+ guard let descriptor = call.getString("descriptor") else {
704
+ call.reject("Descriptor UUID required.")
705
+ return nil
706
+ }
707
+ let descriptorUUID = CBUUID(string: descriptor)
708
+
709
+ return (characteristic.0, characteristic.1, descriptorUUID)
710
+ }
711
+
712
+ private func getBleDevice(_ device: Device) -> BleDevice {
713
+ var bleDevice = [
714
+ "deviceId": device.getId()
715
+ ]
716
+ if let name = device.getName() {
717
+ bleDevice["name"] = name
718
+ }
719
+ return bleDevice
720
+ }
721
+
722
+ private func getScanResult(_ device: Device, _ advertisementData: [String: Any], _ rssi: NSNumber) -> [String: Any] {
723
+ var data = [
724
+ "device": self.getBleDevice(device),
725
+ "rssi": rssi,
726
+ "txPower": advertisementData[CBAdvertisementDataTxPowerLevelKey] ?? 127,
727
+ "uuids": (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? []).map({(uuid) -> String in
728
+ return cbuuidToString(uuid)
729
+ })
730
+ ]
731
+
732
+ if let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
733
+ data["localName"] = localName
734
+ }
735
+
736
+ if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data {
737
+ data["manufacturerData"] = self.getManufacturerData(data: manufacturerData)
738
+ }
739
+
740
+ if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
741
+ data["serviceData"] = self.getServiceData(data: serviceData)
742
+ }
743
+ return data
744
+ }
745
+
746
+ private func getManufacturerData(data: Data) -> [String: String] {
747
+ var company = 0
748
+ var rest = ""
749
+ for (index, byte) in data.enumerated() {
750
+ if index == 0 {
751
+ company += Int(byte)
752
+ } else if index == 1 {
753
+ company += Int(byte) * 256
754
+ } else {
755
+ rest += String(format: "%02hhx ", byte)
756
+ }
757
+ }
758
+ return [String(company): rest]
759
+ }
760
+
761
+ private func getServiceData(data: [CBUUID: Data]) -> [String: String] {
762
+ var result: [String: String] = [:]
763
+ for (key, value) in data {
764
+ result[cbuuidToString(key)] = dataToString(value)
765
+ }
766
+ return result
767
+ }
768
+ }