@capacitor-community/bluetooth-le 7.0.0 → 7.1.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.
Files changed (55) hide show
  1. package/CapacitorCommunityBluetoothLe.podspec +17 -17
  2. package/LICENSE +21 -21
  3. package/README.md +94 -16
  4. package/android/build.gradle +68 -68
  5. package/android/src/main/AndroidManifest.xml +22 -22
  6. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt +1068 -1009
  7. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt +51 -51
  8. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Device.kt +720 -718
  9. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceList.kt +28 -28
  10. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt +189 -189
  11. package/dist/docs.json +926 -78
  12. package/dist/esm/bleClient.d.ts +277 -277
  13. package/dist/esm/bleClient.js +349 -349
  14. package/dist/esm/bleClient.js.map +1 -1
  15. package/dist/esm/config.d.ts +53 -53
  16. package/dist/esm/config.js +2 -2
  17. package/dist/esm/config.js.map +1 -1
  18. package/dist/esm/conversion.d.ts +34 -34
  19. package/dist/esm/conversion.js +84 -84
  20. package/dist/esm/conversion.js.map +1 -1
  21. package/dist/esm/definitions.d.ts +313 -292
  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/index.js.map +1 -1
  27. package/dist/esm/plugin.d.ts +2 -2
  28. package/dist/esm/plugin.js +4 -4
  29. package/dist/esm/plugin.js.map +1 -1
  30. package/dist/esm/queue.d.ts +3 -3
  31. package/dist/esm/queue.js +17 -17
  32. package/dist/esm/queue.js.map +1 -1
  33. package/dist/esm/timeout.d.ts +1 -1
  34. package/dist/esm/timeout.js +9 -9
  35. package/dist/esm/timeout.js.map +1 -1
  36. package/dist/esm/validators.d.ts +1 -1
  37. package/dist/esm/validators.js +11 -11
  38. package/dist/esm/validators.js.map +1 -1
  39. package/dist/esm/web.d.ts +56 -56
  40. package/dist/esm/web.js +340 -335
  41. package/dist/esm/web.js.map +1 -1
  42. package/dist/plugin.cjs.js +839 -834
  43. package/dist/plugin.cjs.js.map +1 -1
  44. package/dist/plugin.js +839 -834
  45. package/dist/plugin.js.map +1 -1
  46. package/ios/Plugin/Conversion.swift +83 -83
  47. package/ios/Plugin/Device.swift +423 -423
  48. package/ios/Plugin/DeviceManager.swift +401 -349
  49. package/ios/Plugin/Info.plist +24 -24
  50. package/ios/Plugin/Logging.swift +8 -8
  51. package/ios/Plugin/Plugin.h +10 -10
  52. package/ios/Plugin/Plugin.m +41 -41
  53. package/ios/Plugin/Plugin.swift +682 -636
  54. package/ios/Plugin/ThreadSafeDictionary.swift +13 -13
  55. package/package.json +101 -101
@@ -1,349 +1,401 @@
1
- import Foundation
2
- import CoreBluetooth
3
-
4
- class DeviceManager: NSObject, CBCentralManagerDelegate {
5
- typealias Callback = (_ success: Bool, _ message: String) -> Void
6
- typealias StateReceiver = (_ enabled: Bool) -> Void
7
- typealias ScanResultCallback = (_ device: Device, _ advertisementData: [String: Any], _ rssi: NSNumber) -> Void
8
-
9
- private var centralManager: CBCentralManager!
10
- private var viewController: UIViewController?
11
- private var displayStrings: [String: String]!
12
- private var callbackMap = [String: Callback]()
13
- private var scanResultCallback: ScanResultCallback?
14
- private var stateReceiver: StateReceiver?
15
- private var timeoutMap = [String: DispatchWorkItem]()
16
- private var stopScanWorkItem: DispatchWorkItem?
17
- private var alertController: UIAlertController?
18
- private var discoveredDevices = [String: Device]()
19
- private var deviceNameFilter: String?
20
- private var deviceNamePrefixFilter: String?
21
- private var shouldShowDeviceList = false
22
- private var allowDuplicates = false
23
-
24
- init(_ viewController: UIViewController?, _ displayStrings: [String: String], _ callback: @escaping Callback) {
25
- super.init()
26
- self.viewController = viewController
27
- self.displayStrings = displayStrings
28
- self.callbackMap["initialize"] = callback
29
- self.centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
30
- }
31
-
32
- func setDisplayStrings(_ displayStrings: [String: String]) {
33
- self.displayStrings = displayStrings
34
- }
35
-
36
- // initialize
37
- func centralManagerDidUpdateState(_ central: CBCentralManager) {
38
- let initializeKey = "initialize"
39
- switch central.state {
40
- case .poweredOn:
41
- self.resolve(initializeKey, "BLE powered on")
42
- self.emitState(enabled: true)
43
- case .poweredOff:
44
- self.stopScan()
45
- self.resolve(initializeKey, "BLE powered off")
46
- self.emitState(enabled: false)
47
- case .resetting:
48
- self.emitState(enabled: false)
49
- case .unauthorized:
50
- self.reject(initializeKey, "BLE permission denied")
51
- self.emitState(enabled: false)
52
- case .unsupported:
53
- self.reject(initializeKey, "BLE unsupported")
54
- self.emitState(enabled: false)
55
- case .unknown:
56
- self.emitState(enabled: false)
57
- default: break
58
- }
59
- }
60
-
61
- func isEnabled() -> Bool {
62
- return self.centralManager.state == CBManagerState.poweredOn
63
- }
64
-
65
- func registerStateReceiver( _ stateReceiver: @escaping StateReceiver) {
66
- self.stateReceiver = stateReceiver
67
- }
68
-
69
- func unregisterStateReceiver() {
70
- self.stateReceiver = nil
71
- }
72
-
73
- func emitState(enabled: Bool) {
74
- guard let stateReceiver = self.stateReceiver else { return }
75
- stateReceiver(enabled)
76
- }
77
-
78
- func startScanning(
79
- _ serviceUUIDs: [CBUUID],
80
- _ name: String?,
81
- _ namePrefix: String?,
82
- _ allowDuplicates: Bool,
83
- _ shouldShowDeviceList: Bool,
84
- _ scanDuration: Double?,
85
- _ callback: @escaping Callback,
86
- _ scanResultCallback: @escaping ScanResultCallback
87
- ) {
88
- self.callbackMap["startScanning"] = callback
89
- self.scanResultCallback = scanResultCallback
90
-
91
- if self.centralManager.isScanning == false {
92
- self.discoveredDevices = [String: Device]()
93
- self.shouldShowDeviceList = shouldShowDeviceList
94
- self.allowDuplicates = allowDuplicates
95
- self.deviceNameFilter = name
96
- self.deviceNamePrefixFilter = namePrefix
97
-
98
- if shouldShowDeviceList {
99
- self.showDeviceList()
100
- }
101
-
102
- if scanDuration != nil {
103
- self.stopScanWorkItem = DispatchWorkItem {
104
- self.stopScan()
105
- }
106
- DispatchQueue.main.asyncAfter(deadline: .now() + scanDuration!, execute: self.stopScanWorkItem!)
107
- }
108
- self.centralManager.scanForPeripherals(
109
- withServices: serviceUUIDs,
110
- options: [CBCentralManagerScanOptionAllowDuplicatesKey: allowDuplicates]
111
- )
112
-
113
- if shouldShowDeviceList == false {
114
- self.resolve("startScanning", "Scan started.")
115
- }
116
- } else {
117
- self.stopScan()
118
- self.reject("startScanning", "Already scanning. Stopping now.")
119
- }
120
- }
121
-
122
- func stopScan() {
123
- log("Stop scanning.")
124
- self.centralManager.stopScan()
125
- self.stopScanWorkItem?.cancel()
126
- self.stopScanWorkItem = nil
127
- DispatchQueue.main.async { [weak self] in
128
- if self?.discoveredDevices.count == 0 {
129
- self?.alertController?.title = self?.displayStrings["noDeviceFound"]
130
- } else {
131
- self?.alertController?.title = self?.displayStrings["availableDevices"]
132
- }
133
- }
134
- }
135
-
136
- // didDiscover
137
- func centralManager(
138
- _ central: CBCentralManager,
139
- didDiscover peripheral: CBPeripheral,
140
- advertisementData: [String: Any],
141
- rssi RSSI: NSNumber
142
- ) {
143
-
144
- guard peripheral.state != CBPeripheralState.connected else {
145
- log("found connected device", peripheral.name ?? "Unknown")
146
- // make sure we do not touch connected devices
147
- return
148
- }
149
-
150
- let isNew = self.discoveredDevices[peripheral.identifier.uuidString] == nil
151
- guard isNew || self.allowDuplicates else { return }
152
-
153
- guard self.passesNameFilter(peripheralName: peripheral.name) else { return }
154
- guard self.passesNamePrefixFilter(peripheralName: peripheral.name) else { return }
155
-
156
- let device: Device
157
- if self.allowDuplicates, let knownDevice = discoveredDevices.first(where: { $0.key == peripheral.identifier.uuidString })?.value {
158
- device = knownDevice
159
- } else {
160
- device = Device(peripheral)
161
- self.discoveredDevices[device.getId()] = device
162
- }
163
- log("New device found: ", device.getName() ?? "Unknown")
164
-
165
- if shouldShowDeviceList {
166
- DispatchQueue.main.async { [weak self] in
167
- self?.alertController?.addAction(UIAlertAction(title: device.getName() ?? "Unknown", style: UIAlertAction.Style.default, handler: { (_) in
168
- log("Selected device")
169
- self?.stopScan()
170
- self?.resolve("startScanning", device.getId())
171
- }))
172
- }
173
- } else {
174
- if self.scanResultCallback != nil {
175
- self.scanResultCallback!(device, advertisementData, RSSI)
176
- }
177
- }
178
- }
179
-
180
- func showDeviceList() {
181
- DispatchQueue.main.async { [weak self] in
182
- self?.alertController = UIAlertController(title: self?.displayStrings["scanning"], message: nil, preferredStyle: UIAlertController.Style.alert)
183
- self?.alertController?.addAction(UIAlertAction(title: self?.displayStrings["cancel"], style: UIAlertAction.Style.cancel, handler: { (_) in
184
- log("Cancelled request device.")
185
- self?.stopScan()
186
- self?.reject("startScanning", "requestDevice cancelled.")
187
- }))
188
- self?.viewController?.present((self?.alertController)!, animated: true, completion: nil)
189
- }
190
- }
191
-
192
- func getDevices(
193
- _ deviceUUIDs: [UUID]
194
- ) -> [CBPeripheral] {
195
- return self.centralManager.retrievePeripherals(withIdentifiers: deviceUUIDs)
196
- }
197
-
198
- func getConnectedDevices(
199
- _ serviceUUIDs: [CBUUID]
200
- ) -> [CBPeripheral] {
201
- return self.centralManager.retrieveConnectedPeripherals(withServices: serviceUUIDs)
202
- }
203
-
204
- func connect(
205
- _ device: Device,
206
- _ connectionTimeout: Double,
207
- _ callback: @escaping Callback
208
- ) {
209
- let key = "connect|\(device.getId())"
210
- self.callbackMap[key] = callback
211
- log("Connecting to peripheral", device.getPeripheral())
212
- self.centralManager.connect(device.getPeripheral(), options: nil)
213
- self.setConnectionTimeout(key, "Connection timeout.", device, connectionTimeout)
214
- }
215
-
216
- // didConnect
217
- func centralManager(
218
- _ central: CBCentralManager,
219
- didConnect peripheral: CBPeripheral
220
- ) {
221
- log("Connected to device", peripheral)
222
- let key = "connect|\(peripheral.identifier.uuidString)"
223
- peripheral.discoverServices(nil)
224
- self.resolve(key, "Successfully connected.")
225
- // will wait for services in plugin call
226
- }
227
-
228
- // didFailToConnect
229
- func centralManager(
230
- _ central: CBCentralManager,
231
- didFailToConnect peripheral: CBPeripheral,
232
- error: Error?
233
- ) {
234
- let key = "connect|\(peripheral.identifier.uuidString)"
235
- if error != nil {
236
- self.reject(key, error!.localizedDescription)
237
- return
238
- }
239
- self.reject(key, "Failed to connect.")
240
- }
241
-
242
- func setOnDisconnected(
243
- _ device: Device,
244
- _ callback: @escaping Callback
245
- ) {
246
- let key = "onDisconnected|\(device.getId())"
247
- self.callbackMap[key] = callback
248
- }
249
-
250
- func disconnect(
251
- _ device: Device,
252
- _ timeout: Double,
253
- _ callback: @escaping Callback
254
- ) {
255
- let key = "disconnect|\(device.getId())"
256
- self.callbackMap[key] = callback
257
- if device.isConnected() == false {
258
- self.resolve(key, "Disconnected.")
259
- return
260
- }
261
- log("Disconnecting from peripheral", device.getPeripheral())
262
- self.centralManager.cancelPeripheralConnection(device.getPeripheral())
263
- self.setTimeout(key, "Disconnection timeout.", timeout)
264
- }
265
-
266
- // didDisconnectPeripheral
267
- func centralManager(
268
- _ central: CBCentralManager,
269
- didDisconnectPeripheral peripheral: CBPeripheral,
270
- error: Error?
271
- ) {
272
- let key = "disconnect|\(peripheral.identifier.uuidString)"
273
- let keyOnDisconnected = "onDisconnected|\(peripheral.identifier.uuidString)"
274
- self.resolve(keyOnDisconnected, "Disconnected.")
275
- if error != nil {
276
- log(error!.localizedDescription)
277
- self.reject(key, error!.localizedDescription)
278
- return
279
- }
280
- self.resolve(key, "Successfully disconnected.")
281
- }
282
-
283
- func getDevice(_ deviceId: String) -> Device? {
284
- return self.discoveredDevices[deviceId]
285
- }
286
-
287
- private func passesNameFilter(peripheralName: String?) -> Bool {
288
- guard let nameFilter = self.deviceNameFilter else { return true }
289
- guard let name = peripheralName else { return false }
290
- return name == nameFilter
291
- }
292
-
293
- private func passesNamePrefixFilter(peripheralName: String?) -> Bool {
294
- guard let prefix = self.deviceNamePrefixFilter else { return true }
295
- guard let name = peripheralName else { return false }
296
- return name.hasPrefix(prefix)
297
- }
298
-
299
- private func resolve(_ key: String, _ value: String) {
300
- let callback = self.callbackMap[key]
301
- if callback != nil {
302
- log("Resolve", key, value)
303
- callback!(true, value)
304
- self.callbackMap[key] = nil
305
- self.timeoutMap[key]?.cancel()
306
- self.timeoutMap[key] = nil
307
- }
308
- }
309
-
310
- private func reject(_ key: String, _ value: String) {
311
- let callback = self.callbackMap[key]
312
- if callback != nil {
313
- log("Reject", key, value)
314
- callback!(false, value)
315
- self.callbackMap[key] = nil
316
- self.timeoutMap[key]?.cancel()
317
- self.timeoutMap[key] = nil
318
- }
319
- }
320
-
321
- private func setTimeout(
322
- _ key: String,
323
- _ message: String,
324
- _ timeout: Double
325
- ) {
326
- let workItem = DispatchWorkItem {
327
- self.reject(key, message)
328
- }
329
- self.timeoutMap[key] = workItem
330
- DispatchQueue.main.asyncAfter(deadline: .now() + timeout, execute: workItem)
331
- }
332
-
333
- private func setConnectionTimeout(
334
- _ key: String,
335
- _ message: String,
336
- _ device: Device,
337
- _ connectionTimeout: Double
338
- ) {
339
- let workItem = DispatchWorkItem {
340
- // do not call onDisconnnected, which is triggered by cancelPeripheralConnection
341
- let key = "onDisconnected|\(device.getId())"
342
- self.callbackMap[key] = nil
343
- self.centralManager.cancelPeripheralConnection(device.getPeripheral())
344
- self.reject(key, message)
345
- }
346
- self.timeoutMap[key] = workItem
347
- DispatchQueue.main.asyncAfter(deadline: .now() + connectionTimeout, execute: workItem)
348
- }
349
- }
1
+ import Foundation
2
+ import CoreBluetooth
3
+
4
+ class DeviceManager: NSObject, CBCentralManagerDelegate {
5
+ typealias Callback = (_ success: Bool, _ message: String) -> Void
6
+ typealias StateReceiver = (_ enabled: Bool) -> Void
7
+ typealias ScanResultCallback = (_ device: Device, _ advertisementData: [String: Any], _ rssi: NSNumber) -> Void
8
+
9
+ private var centralManager: CBCentralManager!
10
+ private var viewController: UIViewController?
11
+ private var displayStrings: [String: String]!
12
+ private var callbackMap = [String: Callback]()
13
+ private var scanResultCallback: ScanResultCallback?
14
+ private var stateReceiver: StateReceiver?
15
+ private var timeoutMap = [String: DispatchWorkItem]()
16
+ private var stopScanWorkItem: DispatchWorkItem?
17
+ private var alertController: UIAlertController?
18
+ private var discoveredDevices = [String: Device]()
19
+ private var deviceNameFilter: String?
20
+ private var deviceNamePrefixFilter: String?
21
+ private var shouldShowDeviceList = false
22
+ private var allowDuplicates = false
23
+ private var manufacturerDataFilters: [ManufacturerDataFilter]?
24
+
25
+ init(_ viewController: UIViewController?, _ displayStrings: [String: String], _ callback: @escaping Callback) {
26
+ super.init()
27
+ self.viewController = viewController
28
+ self.displayStrings = displayStrings
29
+ self.callbackMap["initialize"] = callback
30
+ self.centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
31
+ }
32
+
33
+ func setDisplayStrings(_ displayStrings: [String: String]) {
34
+ self.displayStrings = displayStrings
35
+ }
36
+
37
+ // initialize
38
+ func centralManagerDidUpdateState(_ central: CBCentralManager) {
39
+ let initializeKey = "initialize"
40
+ switch central.state {
41
+ case .poweredOn:
42
+ self.resolve(initializeKey, "BLE powered on")
43
+ self.emitState(enabled: true)
44
+ case .poweredOff:
45
+ self.stopScan()
46
+ self.resolve(initializeKey, "BLE powered off")
47
+ self.emitState(enabled: false)
48
+ case .resetting:
49
+ self.emitState(enabled: false)
50
+ case .unauthorized:
51
+ self.reject(initializeKey, "BLE permission denied")
52
+ self.emitState(enabled: false)
53
+ case .unsupported:
54
+ self.reject(initializeKey, "BLE unsupported")
55
+ self.emitState(enabled: false)
56
+ case .unknown:
57
+ self.emitState(enabled: false)
58
+ default: break
59
+ }
60
+ }
61
+
62
+ func isEnabled() -> Bool {
63
+ return self.centralManager.state == CBManagerState.poweredOn
64
+ }
65
+
66
+ func registerStateReceiver( _ stateReceiver: @escaping StateReceiver) {
67
+ self.stateReceiver = stateReceiver
68
+ }
69
+
70
+ func unregisterStateReceiver() {
71
+ self.stateReceiver = nil
72
+ }
73
+
74
+ func emitState(enabled: Bool) {
75
+ guard let stateReceiver = self.stateReceiver else { return }
76
+ stateReceiver(enabled)
77
+ }
78
+
79
+ func startScanning(
80
+ _ serviceUUIDs: [CBUUID],
81
+ _ name: String?,
82
+ _ namePrefix: String?,
83
+ _ manufacturerDataFilters: [ManufacturerDataFilter]?,
84
+ _ allowDuplicates: Bool,
85
+ _ shouldShowDeviceList: Bool,
86
+ _ scanDuration: Double?,
87
+ _ callback: @escaping Callback,
88
+ _ scanResultCallback: @escaping ScanResultCallback
89
+ ) {
90
+ self.callbackMap["startScanning"] = callback
91
+ self.scanResultCallback = scanResultCallback
92
+
93
+ if self.centralManager.isScanning == false {
94
+ self.discoveredDevices = [String: Device]()
95
+ self.shouldShowDeviceList = shouldShowDeviceList
96
+ self.allowDuplicates = allowDuplicates
97
+ self.deviceNameFilter = name
98
+ self.deviceNamePrefixFilter = namePrefix
99
+ self.manufacturerDataFilters = manufacturerDataFilters
100
+
101
+ if shouldShowDeviceList {
102
+ self.showDeviceList()
103
+ }
104
+
105
+ if scanDuration != nil {
106
+ self.stopScanWorkItem = DispatchWorkItem {
107
+ self.stopScan()
108
+ }
109
+ DispatchQueue.main.asyncAfter(deadline: .now() + scanDuration!, execute: self.stopScanWorkItem!)
110
+ }
111
+ self.centralManager.scanForPeripherals(
112
+ withServices: serviceUUIDs,
113
+ options: [CBCentralManagerScanOptionAllowDuplicatesKey: allowDuplicates]
114
+ )
115
+
116
+ if shouldShowDeviceList == false {
117
+ self.resolve("startScanning", "Scan started.")
118
+ }
119
+ } else {
120
+ self.stopScan()
121
+ self.reject("startScanning", "Already scanning. Stopping now.")
122
+ }
123
+ }
124
+
125
+ func stopScan() {
126
+ log("Stop scanning.")
127
+ self.centralManager.stopScan()
128
+ self.stopScanWorkItem?.cancel()
129
+ self.stopScanWorkItem = nil
130
+ DispatchQueue.main.async { [weak self] in
131
+ if self?.discoveredDevices.count == 0 {
132
+ self?.alertController?.title = self?.displayStrings["noDeviceFound"]
133
+ } else {
134
+ self?.alertController?.title = self?.displayStrings["availableDevices"]
135
+ }
136
+ }
137
+ }
138
+
139
+ // didDiscover
140
+ func centralManager(
141
+ _ central: CBCentralManager,
142
+ didDiscover peripheral: CBPeripheral,
143
+ advertisementData: [String: Any],
144
+ rssi RSSI: NSNumber
145
+ ) {
146
+
147
+ guard peripheral.state != CBPeripheralState.connected else {
148
+ log("found connected device", peripheral.name ?? "Unknown")
149
+ // make sure we do not touch connected devices
150
+ return
151
+ }
152
+
153
+ let isNew = self.discoveredDevices[peripheral.identifier.uuidString] == nil
154
+ guard isNew || self.allowDuplicates else { return }
155
+
156
+ guard self.passesNameFilter(peripheralName: peripheral.name) else { return }
157
+ guard self.passesNamePrefixFilter(peripheralName: peripheral.name) else { return }
158
+ guard self.passesManufacturerDataFilter(advertisementData) else { return }
159
+
160
+ let device: Device
161
+ if self.allowDuplicates, let knownDevice = discoveredDevices.first(where: { $0.key == peripheral.identifier.uuidString })?.value {
162
+ device = knownDevice
163
+ } else {
164
+ device = Device(peripheral)
165
+ self.discoveredDevices[device.getId()] = device
166
+ }
167
+ log("New device found: ", device.getName() ?? "Unknown")
168
+
169
+ if shouldShowDeviceList {
170
+ DispatchQueue.main.async { [weak self] in
171
+ self?.alertController?.addAction(UIAlertAction(title: device.getName() ?? "Unknown", style: UIAlertAction.Style.default, handler: { (_) in
172
+ log("Selected device")
173
+ self?.stopScan()
174
+ self?.resolve("startScanning", device.getId())
175
+ }))
176
+ }
177
+ } else {
178
+ if self.scanResultCallback != nil {
179
+ self.scanResultCallback!(device, advertisementData, RSSI)
180
+ }
181
+ }
182
+ }
183
+
184
+ func showDeviceList() {
185
+ DispatchQueue.main.async { [weak self] in
186
+ self?.alertController = UIAlertController(title: self?.displayStrings["scanning"], message: nil, preferredStyle: UIAlertController.Style.alert)
187
+ self?.alertController?.addAction(UIAlertAction(title: self?.displayStrings["cancel"], style: UIAlertAction.Style.cancel, handler: { (_) in
188
+ log("Cancelled request device.")
189
+ self?.stopScan()
190
+ self?.reject("startScanning", "requestDevice cancelled.")
191
+ }))
192
+ self?.viewController?.present((self?.alertController)!, animated: true, completion: nil)
193
+ }
194
+ }
195
+
196
+ func getDevices(
197
+ _ deviceUUIDs: [UUID]
198
+ ) -> [CBPeripheral] {
199
+ return self.centralManager.retrievePeripherals(withIdentifiers: deviceUUIDs)
200
+ }
201
+
202
+ func getConnectedDevices(
203
+ _ serviceUUIDs: [CBUUID]
204
+ ) -> [CBPeripheral] {
205
+ return self.centralManager.retrieveConnectedPeripherals(withServices: serviceUUIDs)
206
+ }
207
+
208
+ func connect(
209
+ _ device: Device,
210
+ _ connectionTimeout: Double,
211
+ _ callback: @escaping Callback
212
+ ) {
213
+ let key = "connect|\(device.getId())"
214
+ self.callbackMap[key] = callback
215
+ log("Connecting to peripheral", device.getPeripheral())
216
+ self.centralManager.connect(device.getPeripheral(), options: nil)
217
+ self.setConnectionTimeout(key, "Connection timeout.", device, connectionTimeout)
218
+ }
219
+
220
+ // didConnect
221
+ func centralManager(
222
+ _ central: CBCentralManager,
223
+ didConnect peripheral: CBPeripheral
224
+ ) {
225
+ log("Connected to device", peripheral)
226
+ let key = "connect|\(peripheral.identifier.uuidString)"
227
+ peripheral.discoverServices(nil)
228
+ self.resolve(key, "Successfully connected.")
229
+ // will wait for services in plugin call
230
+ }
231
+
232
+ // didFailToConnect
233
+ func centralManager(
234
+ _ central: CBCentralManager,
235
+ didFailToConnect peripheral: CBPeripheral,
236
+ error: Error?
237
+ ) {
238
+ let key = "connect|\(peripheral.identifier.uuidString)"
239
+ if error != nil {
240
+ self.reject(key, error!.localizedDescription)
241
+ return
242
+ }
243
+ self.reject(key, "Failed to connect.")
244
+ }
245
+
246
+ func setOnDisconnected(
247
+ _ device: Device,
248
+ _ callback: @escaping Callback
249
+ ) {
250
+ let key = "onDisconnected|\(device.getId())"
251
+ self.callbackMap[key] = callback
252
+ }
253
+
254
+ func disconnect(
255
+ _ device: Device,
256
+ _ timeout: Double,
257
+ _ callback: @escaping Callback
258
+ ) {
259
+ let key = "disconnect|\(device.getId())"
260
+ self.callbackMap[key] = callback
261
+ if device.isConnected() == false {
262
+ self.resolve(key, "Disconnected.")
263
+ return
264
+ }
265
+ log("Disconnecting from peripheral", device.getPeripheral())
266
+ self.centralManager.cancelPeripheralConnection(device.getPeripheral())
267
+ self.setTimeout(key, "Disconnection timeout.", timeout)
268
+ }
269
+
270
+ // didDisconnectPeripheral
271
+ func centralManager(
272
+ _ central: CBCentralManager,
273
+ didDisconnectPeripheral peripheral: CBPeripheral,
274
+ error: Error?
275
+ ) {
276
+ let key = "disconnect|\(peripheral.identifier.uuidString)"
277
+ let keyOnDisconnected = "onDisconnected|\(peripheral.identifier.uuidString)"
278
+ self.resolve(keyOnDisconnected, "Disconnected.")
279
+ if error != nil {
280
+ log(error!.localizedDescription)
281
+ self.reject(key, error!.localizedDescription)
282
+ return
283
+ }
284
+ self.resolve(key, "Successfully disconnected.")
285
+ }
286
+
287
+ func getDevice(_ deviceId: String) -> Device? {
288
+ return self.discoveredDevices[deviceId]
289
+ }
290
+
291
+ private func passesNameFilter(peripheralName: String?) -> Bool {
292
+ guard let nameFilter = self.deviceNameFilter else { return true }
293
+ guard let name = peripheralName else { return false }
294
+ return name == nameFilter
295
+ }
296
+
297
+ private func passesNamePrefixFilter(peripheralName: String?) -> Bool {
298
+ guard let prefix = self.deviceNamePrefixFilter else { return true }
299
+ guard let name = peripheralName else { return false }
300
+ return name.hasPrefix(prefix)
301
+ }
302
+
303
+ private func passesManufacturerDataFilter(_ advertisementData: [String: Any]) -> Bool {
304
+ guard let filters = self.manufacturerDataFilters, !filters.isEmpty else {
305
+ return true // No filters means everything passes
306
+ }
307
+
308
+ guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data,
309
+ manufacturerData.count >= 2 else {
310
+ return false // If there's no valid manufacturer data, fail
311
+ }
312
+
313
+ let companyIdentifier = manufacturerData.prefix(2).withUnsafeBytes {
314
+ $0.load(as: UInt16.self).littleEndian // Manufacturer ID is little-endian
315
+ }
316
+
317
+ let payload = manufacturerData.dropFirst(2)
318
+
319
+ for filter in filters {
320
+ if filter.companyIdentifier != companyIdentifier {
321
+ continue // Skip if company ID does not match
322
+ }
323
+
324
+ if let dataPrefix = filter.dataPrefix {
325
+ if payload.count < dataPrefix.count {
326
+ continue // Payload too short, does not match
327
+ }
328
+
329
+ if let mask = filter.mask {
330
+ var matches = true
331
+ for i in 0..<dataPrefix.count {
332
+ if (payload[i] & mask[i]) != (dataPrefix[i] & mask[i]) {
333
+ matches = false
334
+ break
335
+ }
336
+ }
337
+ if matches {
338
+ return true
339
+ }
340
+ } else if payload.starts(with: dataPrefix) {
341
+ return true
342
+ }
343
+ } else {
344
+ return true // Company ID matched, and no dataPrefix required
345
+ }
346
+ }
347
+
348
+ return false // If none matched, return false
349
+ }
350
+
351
+ private func resolve(_ key: String, _ value: String) {
352
+ let callback = self.callbackMap[key]
353
+ if callback != nil {
354
+ log("Resolve", key, value)
355
+ callback!(true, value)
356
+ self.callbackMap[key] = nil
357
+ self.timeoutMap[key]?.cancel()
358
+ self.timeoutMap[key] = nil
359
+ }
360
+ }
361
+
362
+ private func reject(_ key: String, _ value: String) {
363
+ let callback = self.callbackMap[key]
364
+ if callback != nil {
365
+ log("Reject", key, value)
366
+ callback!(false, value)
367
+ self.callbackMap[key] = nil
368
+ self.timeoutMap[key]?.cancel()
369
+ self.timeoutMap[key] = nil
370
+ }
371
+ }
372
+
373
+ private func setTimeout(
374
+ _ key: String,
375
+ _ message: String,
376
+ _ timeout: Double
377
+ ) {
378
+ let workItem = DispatchWorkItem {
379
+ self.reject(key, message)
380
+ }
381
+ self.timeoutMap[key] = workItem
382
+ DispatchQueue.main.asyncAfter(deadline: .now() + timeout, execute: workItem)
383
+ }
384
+
385
+ private func setConnectionTimeout(
386
+ _ key: String,
387
+ _ message: String,
388
+ _ device: Device,
389
+ _ connectionTimeout: Double
390
+ ) {
391
+ let workItem = DispatchWorkItem {
392
+ // do not call onDisconnnected, which is triggered by cancelPeripheralConnection
393
+ let key = "onDisconnected|\(device.getId())"
394
+ self.callbackMap[key] = nil
395
+ self.centralManager.cancelPeripheralConnection(device.getPeripheral())
396
+ self.reject(key, message)
397
+ }
398
+ self.timeoutMap[key] = workItem
399
+ DispatchQueue.main.asyncAfter(deadline: .now() + connectionTimeout, execute: workItem)
400
+ }
401
+ }