@blueid/access-capacitor 0.104.0 → 0.105.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.
Files changed (35) hide show
  1. package/BlueidAccessCapacitor.podspec +2 -1
  2. package/dist/esm/BlueCore_pb.d.ts +8 -0
  3. package/dist/esm/BlueCore_pb.js +10 -0
  4. package/dist/esm/BlueCore_pb.js.map +1 -1
  5. package/dist/esm/BlueSDK_pb.d.ts +48 -0
  6. package/dist/esm/BlueSDK_pb.js +12 -0
  7. package/dist/esm/BlueSDK_pb.js.map +1 -1
  8. package/dist/plugin.cjs.js +22 -0
  9. package/dist/plugin.cjs.js.map +1 -1
  10. package/dist/plugin.js +22 -0
  11. package/dist/plugin.js.map +1 -1
  12. package/ios/CBlueIDAccess.xcframework/Info.plist +8 -8
  13. package/ios/CBlueIDAccess.xcframework/ios-arm64/Headers/core/BlueCore.pb.h +2 -0
  14. package/ios/CBlueIDAccess.xcframework/ios-arm64/libCBlueIDAccess.a +0 -0
  15. package/ios/CBlueIDAccess.xcframework/ios-arm64_x86_64-simulator/Headers/core/BlueCore.pb.h +2 -0
  16. package/ios/CBlueIDAccess.xcframework/ios-arm64_x86_64-simulator/libCBlueIDAccess.a +0 -0
  17. package/ios/CBlueIDAccess.xcframework/macos-arm64_x86_64/Headers/core/BlueCore.pb.h +2 -0
  18. package/ios/CBlueIDAccess.xcframework/macos-arm64_x86_64/libCBlueIDAccess.a +0 -0
  19. package/ios/Plugin/BlueIDAccessSDK/BlueAPI.swift +7 -0
  20. package/ios/Plugin/BlueIDAccessSDK/BlueAPIProtocol.swift +13 -0
  21. package/ios/Plugin/BlueIDAccessSDK/BlueAccess.swift +12 -24
  22. package/ios/Plugin/BlueIDAccessSDK/BlueCommands.swift +3 -0
  23. package/ios/Plugin/BlueIDAccessSDK/BlueCore.pb.swift +8 -0
  24. package/ios/Plugin/BlueIDAccessSDK/BlueDFU/BlueDFUPeripheralService.swift +73 -0
  25. package/ios/Plugin/BlueIDAccessSDK/BlueDFU/BlueUpdateAccessDeviceFirmwareCommand.swift +226 -0
  26. package/ios/Plugin/BlueIDAccessSDK/BlueDevices.swift +24 -0
  27. package/ios/Plugin/BlueIDAccessSDK/BlueError.swift +1 -1
  28. package/ios/Plugin/BlueIDAccessSDK/BlueFetch.swift +10 -2
  29. package/ios/Plugin/BlueIDAccessSDK/BlueModal/BlueModal.swift +30 -5
  30. package/ios/Plugin/BlueIDAccessSDK/BlueModal/{BlueSynchronizeAccessDeviceModalSession.swift → BlueStepProgressModalSession.swift} +4 -4
  31. package/ios/Plugin/BlueIDAccessSDK/BlueModal/{BlueSynchronizeAccessDeviceModalView.swift → BlueStepProgressModalView.swift} +27 -11
  32. package/ios/Plugin/BlueIDAccessSDK/BlueSDK.pb.swift +216 -0
  33. package/ios/Plugin/BlueIDAccessSDK/BlueTaskRunner.swift +34 -3
  34. package/ios/Plugin/BlueIDAccessSDK/BlueZip.swift +30 -0
  35. package/package.json +1 -1
@@ -10,16 +10,15 @@
10
10
  <key>HeadersPath</key>
11
11
  <string>Headers</string>
12
12
  <key>LibraryIdentifier</key>
13
- <string>macos-arm64_x86_64</string>
13
+ <string>ios-arm64</string>
14
14
  <key>LibraryPath</key>
15
15
  <string>libCBlueIDAccess.a</string>
16
16
  <key>SupportedArchitectures</key>
17
17
  <array>
18
18
  <string>arm64</string>
19
- <string>x86_64</string>
20
19
  </array>
21
20
  <key>SupportedPlatform</key>
22
- <string>macos</string>
21
+ <string>ios</string>
23
22
  </dict>
24
23
  <dict>
25
24
  <key>BinaryPath</key>
@@ -27,15 +26,18 @@
27
26
  <key>HeadersPath</key>
28
27
  <string>Headers</string>
29
28
  <key>LibraryIdentifier</key>
30
- <string>ios-arm64</string>
29
+ <string>ios-arm64_x86_64-simulator</string>
31
30
  <key>LibraryPath</key>
32
31
  <string>libCBlueIDAccess.a</string>
33
32
  <key>SupportedArchitectures</key>
34
33
  <array>
35
34
  <string>arm64</string>
35
+ <string>x86_64</string>
36
36
  </array>
37
37
  <key>SupportedPlatform</key>
38
38
  <string>ios</string>
39
+ <key>SupportedPlatformVariant</key>
40
+ <string>simulator</string>
39
41
  </dict>
40
42
  <dict>
41
43
  <key>BinaryPath</key>
@@ -43,7 +45,7 @@
43
45
  <key>HeadersPath</key>
44
46
  <string>Headers</string>
45
47
  <key>LibraryIdentifier</key>
46
- <string>ios-arm64_x86_64-simulator</string>
48
+ <string>macos-arm64_x86_64</string>
47
49
  <key>LibraryPath</key>
48
50
  <string>libCBlueIDAccess.a</string>
49
51
  <key>SupportedArchitectures</key>
@@ -52,9 +54,7 @@
52
54
  <string>x86_64</string>
53
55
  </array>
54
56
  <key>SupportedPlatform</key>
55
- <string>ios</string>
56
- <key>SupportedPlatformVariant</key>
57
- <string>simulator</string>
57
+ <string>macos</string>
58
58
  </dict>
59
59
  </array>
60
60
  <key>CFBundlePackageType</key>
@@ -110,6 +110,8 @@ typedef enum BlueReturnCode {
110
110
  BlueReturnCode_SdkGetBlacklistEntriesFailed = -419,
111
111
  BlueReturnCode_SdkGetSystemStatusFailed = -420,
112
112
  BlueReturnCode_SdkWaitDeviceToRestartFailed = -421,
113
+ BlueReturnCode_SdkUnzipError = -422,
114
+ BlueReturnCode_SdkInvalidFirmwareURL = -423,
113
115
  BlueReturnCode_OssMAReturnCodeStart = -1000,
114
116
  BlueReturnCode_OssMAReturnCodeEnd = -1100
115
117
  } BlueReturnCode_t;
@@ -110,6 +110,8 @@ typedef enum BlueReturnCode {
110
110
  BlueReturnCode_SdkGetBlacklistEntriesFailed = -419,
111
111
  BlueReturnCode_SdkGetSystemStatusFailed = -420,
112
112
  BlueReturnCode_SdkWaitDeviceToRestartFailed = -421,
113
+ BlueReturnCode_SdkUnzipError = -422,
114
+ BlueReturnCode_SdkInvalidFirmwareURL = -423,
113
115
  BlueReturnCode_OssMAReturnCodeStart = -1000,
114
116
  BlueReturnCode_OssMAReturnCodeEnd = -1100
115
117
  } BlueReturnCode_t;
@@ -110,6 +110,8 @@ typedef enum BlueReturnCode {
110
110
  BlueReturnCode_SdkGetBlacklistEntriesFailed = -419,
111
111
  BlueReturnCode_SdkGetSystemStatusFailed = -420,
112
112
  BlueReturnCode_SdkWaitDeviceToRestartFailed = -421,
113
+ BlueReturnCode_SdkUnzipError = -422,
114
+ BlueReturnCode_SdkInvalidFirmwareURL = -423,
113
115
  BlueReturnCode_OssMAReturnCodeStart = -1000,
114
116
  BlueReturnCode_OssMAReturnCodeEnd = -1100
115
117
  } BlueReturnCode_t;
@@ -100,6 +100,13 @@ class BlueAPI: BlueAPIProtocol {
100
100
  )
101
101
  }
102
102
 
103
+ func getLatestFirmware(deviceID: String, with tokenAuthentication: BlueTokenAuthentication) async throws -> BlueFetchResponse<BlueGetLatestFirmwareResult> {
104
+ return try await post(
105
+ endpoint: .AccessGetLatestFirmware,
106
+ request: BlueGetLatestFirmwareRequest(deviceId: deviceID, tokenAuthentication: tokenAuthentication)
107
+ )
108
+ }
109
+
103
110
  private func post<T>(endpoint: BlueAPIEndpoints, request: Encodable) async throws -> BlueFetchResponse<T> {
104
111
  guard #available(macOS 12.0, *) else {
105
112
  throw BlueError(.sdkUnsupportedPlatform)
@@ -18,6 +18,7 @@ public enum BlueAPIEndpoints: String {
18
18
  case AccessCredentials = "/access/credentials"
19
19
  case AccessSynchronizeOfflineAccess = "/access/synchronizeOfflineAccess"
20
20
  case AccessClaimCredential = "/access/cc"
21
+ case AccessGetLatestFirmware = "/access/getLatestFirmware"
21
22
 
22
23
  var url: URL {
23
24
  guard let url = URL(string: baseURL) else {
@@ -236,6 +237,17 @@ internal struct BlueAccessToken: Codable {
236
237
  var expiresAt: Int
237
238
  }
238
239
 
240
+ /// [POST] /access/getLatestFirmware request
241
+ internal struct BlueGetLatestFirmwareRequest: Encodable {
242
+ var deviceId: String
243
+ var tokenAuthentication: BlueTokenAuthentication
244
+ }
245
+ /// [POST] /access/getLatestFirmware response
246
+ internal struct BlueGetLatestFirmwareResult: Decodable {
247
+ var version: Int
248
+ var url: String
249
+ }
250
+
239
251
  protocol BlueAPIProtocol {
240
252
  func getAccessToken(credentialId: String) async throws -> BlueFetchResponse<BlueAccessToken>
241
253
  func synchronizeMobileAccess(with tokenAuthentication: BlueTokenAuthentication, forceRefresh: Bool?) async throws -> BlueFetchResponse<BlueMobileAccessSynchronizationResult>
@@ -250,4 +262,5 @@ protocol BlueAPIProtocol {
250
262
  func claimDevice(deviceID: String, objectID: String, with tokenAuthentication: BlueTokenAuthentication) async throws -> BlueFetchResponse<BlueClaimDeviceResult>
251
263
  func claimAccessCredential(activationToken: String) async throws -> BlueFetchResponse<BlueClaimAccessCredentialResult>
252
264
  func getAccessCredentials(with tokenAuthentication: BlueTokenAuthentication) async throws -> BlueFetchResponse<BlueGetAccessCredentialsResult>
265
+ func getLatestFirmware(deviceID: String, with tokenAuthentication: BlueTokenAuthentication) async throws -> BlueFetchResponse<BlueGetLatestFirmwareResult>
253
266
  }
@@ -162,14 +162,14 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
162
162
  BlueTask(
163
163
  id: BlueSynchronizeAccessTaskId.getAuthenticationToken,
164
164
  label: blueI18n.syncDeviceGetAuthenticationTokenTaskLabel
165
- ) { _ in
165
+ ) { _, _ in
166
166
  .result(try await self.getAuthenticationToken(credential))
167
167
  },
168
168
 
169
169
  BlueTask(
170
170
  id: BlueSynchronizeAccessTaskId.getDeviceConfig,
171
171
  label: blueI18n.syncDeviceRetrieveDeviceConfigurationTaskLabel
172
- ) { runner in
172
+ ) { _, runner in
173
173
  let tokenAuthentication: BlueTokenAuthentication = try runner.getResult(BlueSynchronizeAccessTaskId.getAuthenticationToken)
174
174
 
175
175
  return .result(try await self.getBlueSystemConfig(deviceID: deviceID, with: tokenAuthentication))
@@ -178,7 +178,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
178
178
  BlueTask(
179
179
  id: BlueSynchronizeAccessTaskId.updateDeviceConfig,
180
180
  label: blueI18n.syncDeviceUpdateDeviceConfigurationTaskLabel
181
- ) { runner in
181
+ ) { _, runner in
182
182
  let config: BlueSystemConfig? = try runner.getResult(BlueSynchronizeAccessTaskId.getDeviceConfig)
183
183
 
184
184
  let status: BlueSystemStatus = try await self.updateDevice(deviceID, config)
@@ -191,7 +191,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
191
191
  BlueTask(
192
192
  id: BlueSynchronizeAccessTaskId.updateDeviceTime,
193
193
  label: blueI18n.syncDeviceUpdateDeviceTimeTaskLabel
194
- ) { runner in
194
+ ) { _, runner in
195
195
  let status: BlueSystemStatus = try runner.getResult(BlueSynchronizeAccessTaskId.updateDeviceConfig)
196
196
 
197
197
  return .resultWithStatus(nil, status.settings.timeWasSet == true ? .succeeded : .skipped)
@@ -200,7 +200,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
200
200
  BlueTask(
201
201
  id: BlueSynchronizeAccessTaskId.waitForRestart,
202
202
  label: blueI18n.syncDeviceWaitForDeviceToRestartTaskLabel
203
- ) { _ in
203
+ ) { _, _ in
204
204
  .result(try await self.waitUntilDeviceHasBeenRestarted(deviceID))
205
205
  },
206
206
 
@@ -208,7 +208,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
208
208
  id: BlueSynchronizeAccessTaskId.pushEventLogs,
209
209
  label: blueI18n.syncDevicePushEventLogsTaskLabel,
210
210
  failable: true
211
- ) { runner in
211
+ ) { _, runner in
212
212
  let tokenAuthentication: BlueTokenAuthentication = try runner.getResult(BlueSynchronizeAccessTaskId.getAuthenticationToken)
213
213
  let status: BlueSystemStatus = try runner.getResult(BlueSynchronizeAccessTaskId.updateDeviceConfig)
214
214
 
@@ -219,7 +219,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
219
219
  id: BlueSynchronizeAccessTaskId.pushSystemLogs,
220
220
  label: blueI18n.syncDevicePushSystemLogsTaskLabel,
221
221
  failable: true
222
- ) { runner in
222
+ ) { _, runner in
223
223
  let tokenAuthentication: BlueTokenAuthentication = try runner.getResult(BlueSynchronizeAccessTaskId.getAuthenticationToken)
224
224
  let status: BlueSystemStatus = try runner.getResult(BlueSynchronizeAccessTaskId.updateDeviceConfig)
225
225
 
@@ -230,7 +230,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
230
230
  id: BlueSynchronizeAccessTaskId.getBlacklistEntries,
231
231
  label: blueI18n.syncDeviceRetrieveBlacklistEntriesTaskLabel,
232
232
  failable: true
233
- ) { runner in
233
+ ) { _, runner in
234
234
  let tokenAuthentication: BlueTokenAuthentication = try runner.getResult(BlueSynchronizeAccessTaskId.getAuthenticationToken)
235
235
 
236
236
  return .result(try await self.getBlacklistEntries(deviceID, tokenAuthentication))
@@ -240,7 +240,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
240
240
  id: BlueSynchronizeAccessTaskId.deployBlacklistEntries,
241
241
  label: blueI18n.syncDeviceDeployBlacklistEntriesTaskLabel,
242
242
  failable: true
243
- ) { runner in
243
+ ) { _, runner in
244
244
  let entries: BlueBlacklistEntries? = try runner.getResult(BlueSynchronizeAccessTaskId.getBlacklistEntries)
245
245
 
246
246
  guard let entries = entries else {
@@ -253,14 +253,14 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
253
253
  BlueTask(
254
254
  id: BlueSynchronizeAccessTaskId.getSystemStatus,
255
255
  label: blueI18n.syncDeviceRetrieveSystemStatusTaskLabel
256
- ) { _ in
256
+ ) { _, _ in
257
257
  .result(try await self.getSystemStatus(deviceID))
258
258
  },
259
259
 
260
260
  BlueTask(
261
261
  id: BlueSynchronizeAccessTaskId.pushSystemStatus,
262
262
  label: blueI18n.syncDevicePushSystemStatusTaskLabel
263
- ) { runner in
263
+ ) { _, runner in
264
264
  let tokenAuthentication: BlueTokenAuthentication = try runner.getResult(BlueSynchronizeAccessTaskId.getAuthenticationToken)
265
265
  let status: BlueSystemStatus = try runner.getResult(BlueSynchronizeAccessTaskId.getSystemStatus)
266
266
 
@@ -321,19 +321,7 @@ public class BlueSynchronizeAccessDeviceCommand: BlueSdkAsyncCommand {
321
321
 
322
322
  private func waitUntilDeviceHasBeenRestarted(_ deviceID: String) async throws {
323
323
  do {
324
- var attempts = 0
325
-
326
- while attempts <= 2 {
327
- try? await Task.sleep(nanoseconds: UInt64(blueSecondsToNanoseconds(10)))
328
-
329
- if blueGetDevice(deviceID) != nil {
330
- return
331
- }
332
-
333
- attempts += 1
334
- }
335
-
336
- throw BlueError(.sdkDeviceNotFound)
324
+ try await waitForDeviceAvailability(deviceID)
337
325
  } catch {
338
326
  throw BlueError(.sdkWaitDeviceToRestartFailed, cause: error)
339
327
  }
@@ -93,6 +93,9 @@ public struct BlueCommands {
93
93
 
94
94
  public let openAppSettings = BlueOpenAppSettingsCommand()
95
95
 
96
+ // MARK: - DFU commands
97
+ public let updateAccessDeviceFirmware = BlueUpdateAccessDeviceFirmwareCommand(defaultSdkService)
98
+
96
99
  // TODO: Make it available only for dev environment
97
100
  public let UNSAFE_clearData = BlueClearDataCommand()
98
101
 
@@ -129,6 +129,8 @@ public enum BlueReturnCode: SwiftProtobuf.Enum {
129
129
  case sdkGetBlacklistEntriesFailed // = -419
130
130
  case sdkGetSystemStatusFailed // = -420
131
131
  case sdkWaitDeviceToRestartFailed // = -421
132
+ case sdkUnzipError // = -422
133
+ case sdkInvalidFirmwareURL // = -423
132
134
  case ossMareturnCodeStart // = -1000
133
135
  case ossMareturnCodeEnd // = -1100
134
136
 
@@ -140,6 +142,8 @@ public enum BlueReturnCode: SwiftProtobuf.Enum {
140
142
  switch rawValue {
141
143
  case -1100: self = .ossMareturnCodeEnd
142
144
  case -1000: self = .ossMareturnCodeStart
145
+ case -423: self = .sdkInvalidFirmwareURL
146
+ case -422: self = .sdkUnzipError
143
147
  case -421: self = .sdkWaitDeviceToRestartFailed
144
148
  case -420: self = .sdkGetSystemStatusFailed
145
149
  case -419: self = .sdkGetBlacklistEntriesFailed
@@ -243,6 +247,8 @@ public enum BlueReturnCode: SwiftProtobuf.Enum {
243
247
  switch self {
244
248
  case .ossMareturnCodeEnd: return -1100
245
249
  case .ossMareturnCodeStart: return -1000
250
+ case .sdkInvalidFirmwareURL: return -423
251
+ case .sdkUnzipError: return -422
246
252
  case .sdkWaitDeviceToRestartFailed: return -421
247
253
  case .sdkGetSystemStatusFailed: return -420
248
254
  case .sdkGetBlacklistEntriesFailed: return -419
@@ -3643,6 +3649,8 @@ extension BlueReturnCode: SwiftProtobuf._ProtoNameProviding {
3643
3649
  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
3644
3650
  -1100: .same(proto: "OssMAReturnCodeEnd"),
3645
3651
  -1000: .same(proto: "OssMAReturnCodeStart"),
3652
+ -423: .same(proto: "SdkInvalidFirmwareURL"),
3653
+ -422: .same(proto: "SdkUnzipError"),
3646
3654
  -421: .same(proto: "SdkWaitDeviceToRestartFailed"),
3647
3655
  -420: .same(proto: "SdkGetSystemStatusFailed"),
3648
3656
  -419: .same(proto: "SdkGetBlacklistEntriesFailed"),
@@ -0,0 +1,73 @@
1
+ import CoreBluetooth
2
+ import Foundation
3
+ import NordicDFU
4
+
5
+ /**
6
+ * @class BlueDFUPeripheralService
7
+ * A utility class for looking up peripherals that are advertising DFU service.
8
+ */
9
+ internal class BlueDFUPeripheralService: NSObject, CBCentralManagerDelegate {
10
+ private var centralManager: CBCentralManager? = nil
11
+ private var discoveredPeripheral: CBPeripheral?
12
+ private var continuation: CheckedContinuation<CBPeripheral, any Error>?
13
+
14
+ /// Finds a peripheral that is advertising DFU service asynchronously within the specified timeout period.
15
+ ///
16
+ /// - parameter timeout: The duration in seconds to wait for the peripheral to be discovered. Default value is 10.0 seconds.
17
+ /// - returns: A `CBPeripheral` object representing the DFU peripheral if found.
18
+ /// - throws: An error of type `BlueError` if the timeout period elapses without finding the peripheral.
19
+ func find(_ timeout: TimeInterval = 10.0) async throws -> CBPeripheral {
20
+ if self.centralManager != nil {
21
+ throw BlueError(.invalidState)
22
+ }
23
+
24
+ self.centralManager = CBCentralManager(delegate: self, queue: nil)
25
+
26
+ return try await withCheckedThrowingContinuation { continuation in
27
+ self.continuation = continuation
28
+
29
+ self.startScan()
30
+
31
+ DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
32
+ if self.discoveredPeripheral == nil {
33
+ continuation.resume(throwing: BlueError(.sdkTimeout))
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ /// Destroys the BlueDFUPeripheralService instance, releasing any resources it holds.
40
+ func destroy() {
41
+ if let centralManager = centralManager {
42
+ if centralManager.isScanning {
43
+ centralManager.stopScan()
44
+ }
45
+ centralManager.delegate = nil
46
+ }
47
+ }
48
+
49
+ /// Starts scanning for peripherals that are that are advertising DFU service.
50
+ private func startScan() {
51
+ if let centralManager = centralManager {
52
+ if (centralManager.isScanning) {
53
+ centralManager.stopScan()
54
+ }
55
+
56
+ if centralManager.state == .poweredOn {
57
+ centralManager.scanForPeripherals(withServices: [DFUUuidHelper().secureDFUService], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
58
+ }
59
+ }
60
+ }
61
+
62
+ // MARK: - CBCentralManagerDelegate API
63
+
64
+ func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
65
+ self.discoveredPeripheral = peripheral
66
+ self.continuation?.resume(returning: peripheral)
67
+ self.destroy()
68
+ }
69
+
70
+ func centralManagerDidUpdateState(_ central: CBCentralManager) {
71
+ self.startScan()
72
+ }
73
+ }
@@ -0,0 +1,226 @@
1
+ import CoreBluetooth
2
+ import Foundation
3
+ import NordicDFU
4
+
5
+ /**
6
+ * @class BlueUpdateAccessDeviceFirmwareCommand
7
+ * A SDK command for updating firmware of nRF51 and nRF52 devices over Bluetooth LE.
8
+ */
9
+ public class BlueUpdateAccessDeviceFirmwareCommand: BlueSdkAsyncCommand {
10
+ override func runAsync(arg0: Any?, arg1: Any?, arg2: Any?) async throws -> Any? {
11
+ return try await runAsync(
12
+ credentialID: blueCastArg(String.self, arg0),
13
+ deviceID: blueCastArg(String.self, arg1)
14
+ )
15
+ }
16
+
17
+ public func runAsync(credentialID: String, deviceID: String) async throws {
18
+ guard let credential = blueGetAccessCredential(credentialID: credentialID) else {
19
+ throw BlueError(.sdkCredentialNotFound)
20
+ }
21
+
22
+ guard blueGetDevice(deviceID) != nil else {
23
+ throw BlueError(.sdkDeviceNotFound)
24
+ }
25
+
26
+ try await BlueUpdateAccessDeviceFirmware(sdkService)
27
+ .update(credential, deviceID)
28
+ }
29
+ }
30
+
31
+ internal class BlueUpdateAccessDeviceFirmware: LoggerDelegate, DFUServiceDelegate, DFUProgressDelegate {
32
+ public enum BlueUpdateAccessDeviceFirmwareTaskId {
33
+ case getAuthenticationToken
34
+ case checkLatestFirmware
35
+ case downloadLatestFirmware
36
+ case prepareUpdate
37
+ case startBootloader
38
+ case findDFUPeripheral
39
+ case updateFirmware
40
+ case waitRestart
41
+ }
42
+
43
+ private let sdkService: BlueSdkService
44
+
45
+ private var semaphore: DispatchSemaphore?
46
+ private var controller: DFUServiceController?
47
+ private var task: BlueTask?
48
+ private var error: BlueError? = nil
49
+
50
+ init(_ sdkService: BlueSdkService) {
51
+ self.sdkService = sdkService
52
+ }
53
+
54
+ public func update(_ credential: BlueAccessCredential, _ deviceID: String) async throws {
55
+
56
+ let tasks = [
57
+ BlueTask(
58
+ id: BlueUpdateAccessDeviceFirmwareTaskId.getAuthenticationToken,
59
+ label: blueI18n.dfuGetAuthenticationTokenTaskLabel
60
+ ) { _, _ in
61
+ .result(try await self.sdkService.authenticationTokenService.getTokenAuthentication(credential: credential))
62
+ },
63
+
64
+ BlueTask(
65
+ id: BlueUpdateAccessDeviceFirmwareTaskId.checkLatestFirmware,
66
+ label: blueI18n.dfuCheckLatestFwlabel
67
+ ) { _, runner in
68
+ let tokenAuthentication: BlueTokenAuthentication = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.getAuthenticationToken)
69
+
70
+ return .result(try await self.sdkService.apiService.getLatestFirmware(deviceID: deviceID, with: tokenAuthentication).getData())
71
+ },
72
+
73
+ BlueTask(
74
+ id: BlueUpdateAccessDeviceFirmwareTaskId.downloadLatestFirmware,
75
+ label: blueI18n.dfuDownloadLatestFwlabel
76
+ ) { _, runner in
77
+ let latestFW: BlueGetLatestFirmwareResult = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.checkLatestFirmware)
78
+
79
+ return .result(try await self.downloadLatestFirmware(url: latestFW.url))
80
+ },
81
+
82
+ BlueTask(
83
+ id: BlueUpdateAccessDeviceFirmwareTaskId.prepareUpdate,
84
+ label: blueI18n.dfuPrepareUpdateLabel
85
+ ) { _, runner in
86
+ let zip: Data = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.downloadLatestFirmware)
87
+
88
+ return .result(try self.prepareUpdate(zip))
89
+ },
90
+
91
+ BlueTask(
92
+ id: BlueUpdateAccessDeviceFirmwareTaskId.startBootloader,
93
+ label: blueI18n.dfuStartBootloaderLabel
94
+ ) { _, _ in
95
+ return .result(try await self.startBootloader(deviceID))
96
+ },
97
+
98
+ BlueTask(
99
+ id: BlueUpdateAccessDeviceFirmwareTaskId.findDFUPeripheral,
100
+ label: blueI18n.dfuFindDfuperipheralLabel
101
+ ) { _, _ in
102
+ return .result(try await self.findPeripheral())
103
+ },
104
+
105
+ BlueTask(
106
+ id: BlueUpdateAccessDeviceFirmwareTaskId.updateFirmware,
107
+ label: blueI18n.dfuUpdateFwlabel,
108
+ progress: 0,
109
+ cancelHandler: {
110
+ _ = self.controller?.abort()
111
+ },
112
+ handler: { task, runner in
113
+ let urlToZipFile: URL = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.prepareUpdate)
114
+ let peripheral: CBPeripheral = try runner.getResult(BlueUpdateAccessDeviceFirmwareTaskId.findDFUPeripheral)
115
+
116
+ self.task = task
117
+
118
+ return .result(try self.update(peripheral, urlToZipFile))
119
+ }
120
+ ),
121
+
122
+ BlueTask(
123
+ id: BlueUpdateAccessDeviceFirmwareTaskId.waitRestart,
124
+ label: blueI18n.dfuWaitForDeviceToRestartTaskLabel
125
+ ) { _, _ in
126
+ return .result(try await waitForDeviceAvailability(deviceID, timeout: 5, maxRetries: 6))
127
+ }
128
+ ]
129
+
130
+ let runner = BlueSerialTaskRunner(tasks)
131
+
132
+ #if os(iOS) || os(watchOS)
133
+ try await blueShowUpdateAccessDeviceFirmwareModal(runner)
134
+ #else
135
+ try await runner.execute(true)
136
+ #endif
137
+ }
138
+
139
+ private func downloadLatestFirmware(url string: String) async throws -> Data {
140
+ guard let url = URL(string: string) else {
141
+ throw BlueError(.sdkInvalidFirmwareURL)
142
+ }
143
+
144
+ return try await BlueFetch.get(url: url).getData()
145
+ }
146
+
147
+ private func prepareUpdate(_ zip: Data) throws -> URL {
148
+ let extractedURL = try BlueZip.extract(data: zip)
149
+
150
+ return extractedURL.appendingPathComponent("dfu_application.zip")
151
+ }
152
+
153
+ private func startBootloader(_ deviceID: String) async throws {
154
+ try await blueTerminalRun(deviceID: deviceID, action: "BOOTLD")
155
+ }
156
+
157
+ private func findPeripheral() async throws -> CBPeripheral {
158
+ let service = BlueDFUPeripheralService()
159
+
160
+ defer {
161
+ service.destroy()
162
+ }
163
+
164
+ return try await service.find()
165
+ }
166
+
167
+ private func update(_ peripheral: CBPeripheral, _ urlToZipFile: URL) throws {
168
+ self.semaphore = DispatchSemaphore(value: 0)
169
+
170
+ let firmware = try DFUFirmware(urlToZipFile: urlToZipFile)
171
+
172
+ let initiator = DFUServiceInitiator()
173
+ initiator.logger = self
174
+ initiator.delegate = self
175
+ initiator.progressDelegate = self
176
+
177
+ self.controller = initiator
178
+ .with(firmware: firmware)
179
+ .start(targetWithIdentifier: peripheral.identifier)
180
+
181
+ self.semaphore?.wait()
182
+
183
+ if let error = error {
184
+ throw error
185
+ }
186
+ }
187
+
188
+ // MARK: - LoggerDelegate API
189
+
190
+ public func logWith(_ level: NordicDFU.LogLevel, message: String) {
191
+ switch (level) {
192
+ case .debug, .application, .verbose:
193
+ blueLogDebug(message)
194
+ break
195
+ case .info:
196
+ blueLogInfo(message)
197
+ break
198
+ case .warning:
199
+ blueLogWarn(message)
200
+ break
201
+ case .error:
202
+ blueLogError(message)
203
+ break
204
+ }
205
+ }
206
+
207
+ // MARK: - DFUServiceDelegate API
208
+
209
+ public func dfuStateDidChange(to state: NordicDFU.DFUState) {
210
+ if state == .completed || state == .aborted {
211
+ semaphore?.signal()
212
+ }
213
+ }
214
+
215
+ public func dfuError(_ error: NordicDFU.DFUError, didOccurWithMessage message: String) {
216
+ self.error = BlueError(.error, detail: message)
217
+
218
+ semaphore?.signal()
219
+ }
220
+
221
+ // MARK: - DFUProgressDelegate API
222
+
223
+ public func dfuProgressDidChange(for part: Int, outOf totalParts: Int, to progress: Int, currentSpeedBytesPerSecond: Double, avgSpeedBytesPerSecond: Double) {
224
+ self.task?.updateProgress(Float(progress))
225
+ }
226
+ }
@@ -10,6 +10,30 @@ internal func blueSetMaxDeviceAgeSeconds(_ newMaxDeviceAgeSeconds: Double) {
10
10
  maxDeviceAgeSeconds = max(newMaxDeviceAgeSeconds, 1)
11
11
  }
12
12
 
13
+ /// Waits asynchronously for a device with the specified ID to become available.
14
+ /// This function waits for a device with the given ID to be discovered within a predefined timeout period, with a maximum number of retries.
15
+ /// - parameters:
16
+ /// - deviceID: The ID of the device to wait for.
17
+ /// - timeout: The duration in seconds to wait for the device to be discovered each time. Default value is 10 seconds.
18
+ /// - maxRetries: The maximum number of retries before giving up. Default value is 3.
19
+ /// - throws: An error of type `BlueError` if the device is not found within the specified timeout period and maximum retries.
20
+
21
+ internal func waitForDeviceAvailability(_ deviceID: String, timeout: Int = 10, maxRetries: Int = 3) async throws {
22
+ var attempts = 0
23
+
24
+ while attempts < maxRetries {
25
+ try? await Task.sleep(nanoseconds: UInt64(blueSecondsToNanoseconds(timeout)))
26
+
27
+ if blueGetDevice(deviceID) != nil {
28
+ return
29
+ }
30
+
31
+ attempts += 1
32
+ }
33
+
34
+ throw BlueError(.sdkDeviceNotFound)
35
+ }
36
+
13
37
  internal func blueGetDevice(_ deviceID: String) -> BlueDevice? {
14
38
  for device in blueDevices {
15
39
  if (device.info.deviceID == deviceID) {
@@ -45,7 +45,7 @@ public final class BlueError: Error, LocalizedError, Equatable {
45
45
  self.detail = nil
46
46
  }
47
47
 
48
- public init(_ returnCode: BlueReturnCode, cause: Error, detail: String? = nil) {
48
+ public init(_ returnCode: BlueReturnCode, cause: Error? = nil, detail: String? = nil) {
49
49
  self.returnCode = returnCode
50
50
  self.cause = cause
51
51
  self.detail = detail