@bota.dev/react-native-app-sdk 2.0.0-beta.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.
- package/BotaDeviceSDK.podspec +55 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/android/build.gradle +76 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +172 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycle.kt +100 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +82 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidModels.kt +476 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTA.kt +58 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +180 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +751 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurity.kt +509 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFi.kt +125 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +46 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +723 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKPackage.kt +33 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +211 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycleTest.kt +132 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +57 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTATest.kt +75 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +147 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +355 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurityTest.kt +523 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFiTest.kt +100 -0
- package/generated/codegen-contract.json +3593 -0
- package/ios/BotaDeviceSDK.mm +1300 -0
- package/ios/BotaDeviceSDKAppleBridge.swift +2260 -0
- package/ios/BotaDeviceSDKAppleDevices.swift +191 -0
- package/ios/BotaDeviceSDKAppleLifecycle.swift +167 -0
- package/ios/BotaDeviceSDKAppleLogs.swift +79 -0
- package/ios/BotaDeviceSDKAppleOTA.swift +73 -0
- package/ios/BotaDeviceSDKAppleRecordingUploads.swift +178 -0
- package/ios/BotaDeviceSDKAppleRecordings.swift +670 -0
- package/ios/BotaDeviceSDKAppleSecurity.swift +586 -0
- package/ios/BotaDeviceSDKAppleWiFi.swift +128 -0
- package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +99 -0
- package/lib/commonjs/BotaClient.js +194 -0
- package/lib/commonjs/BotaClient.js.map +1 -0
- package/lib/commonjs/ble/constants.js +10 -0
- package/lib/commonjs/ble/constants.js.map +1 -0
- package/lib/commonjs/ble/deviceLogs.js +50 -0
- package/lib/commonjs/ble/deviceLogs.js.map +1 -0
- package/lib/commonjs/cache/DeviceStateCache.js +6 -0
- package/lib/commonjs/cache/DeviceStateCache.js.map +1 -0
- package/lib/commonjs/client.js +746 -0
- package/lib/commonjs/client.js.map +1 -0
- package/lib/commonjs/compatibility/runtime.js +44 -0
- package/lib/commonjs/compatibility/runtime.js.map +1 -0
- package/lib/commonjs/index.js +123 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/managers/DeviceManager.js +647 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -0
- package/lib/commonjs/managers/OTAManager.js +140 -0
- package/lib/commonjs/managers/OTAManager.js.map +1 -0
- package/lib/commonjs/managers/RecordingManager.js +444 -0
- package/lib/commonjs/managers/RecordingManager.js.map +1 -0
- package/lib/commonjs/managers/StreamingSession.js +175 -0
- package/lib/commonjs/managers/StreamingSession.js.map +1 -0
- package/lib/commonjs/managers/types.js +6 -0
- package/lib/commonjs/managers/types.js.map +1 -0
- package/lib/commonjs/models/Device.js +2 -0
- package/lib/commonjs/models/Device.js.map +1 -0
- package/lib/commonjs/models/Recording.js +2 -0
- package/lib/commonjs/models/Recording.js.map +1 -0
- package/lib/commonjs/models/Status.js +6 -0
- package/lib/commonjs/models/Status.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js +9 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -0
- package/lib/commonjs/sync/syncStatus.js +178 -0
- package/lib/commonjs/sync/syncStatus.js.map +1 -0
- package/lib/commonjs/utils/errors.js +194 -0
- package/lib/commonjs/utils/errors.js.map +1 -0
- package/lib/commonjs/utils/logger.js +84 -0
- package/lib/commonjs/utils/logger.js.map +1 -0
- package/lib/module/BotaClient.js +189 -0
- package/lib/module/BotaClient.js.map +1 -0
- package/lib/module/ble/constants.js +6 -0
- package/lib/module/ble/constants.js.map +1 -0
- package/lib/module/ble/deviceLogs.js +45 -0
- package/lib/module/ble/deviceLogs.js.map +1 -0
- package/lib/module/cache/DeviceStateCache.js +4 -0
- package/lib/module/cache/DeviceStateCache.js.map +1 -0
- package/lib/module/client.js +740 -0
- package/lib/module/client.js.map +1 -0
- package/lib/module/compatibility/runtime.js +35 -0
- package/lib/module/compatibility/runtime.js.map +1 -0
- package/lib/module/index.js +17 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/managers/DeviceManager.js +641 -0
- package/lib/module/managers/DeviceManager.js.map +1 -0
- package/lib/module/managers/OTAManager.js +134 -0
- package/lib/module/managers/OTAManager.js.map +1 -0
- package/lib/module/managers/RecordingManager.js +438 -0
- package/lib/module/managers/RecordingManager.js.map +1 -0
- package/lib/module/managers/StreamingSession.js +168 -0
- package/lib/module/managers/StreamingSession.js.map +1 -0
- package/lib/module/managers/types.js +4 -0
- package/lib/module/managers/types.js.map +1 -0
- package/lib/module/models/Device.js +2 -0
- package/lib/module/models/Device.js.map +1 -0
- package/lib/module/models/Recording.js +2 -0
- package/lib/module/models/Recording.js.map +1 -0
- package/lib/module/models/Status.js +4 -0
- package/lib/module/models/Status.js.map +1 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js +5 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -0
- package/lib/module/sync/syncStatus.js +174 -0
- package/lib/module/sync/syncStatus.js.map +1 -0
- package/lib/module/utils/errors.js +183 -0
- package/lib/module/utils/errors.js.map +1 -0
- package/lib/module/utils/logger.js +80 -0
- package/lib/module/utils/logger.js.map +1 -0
- package/lib/typescript/BotaClient.d.ts +40 -0
- package/lib/typescript/BotaClient.d.ts.map +1 -0
- package/lib/typescript/ble/constants.d.ts +4 -0
- package/lib/typescript/ble/constants.d.ts.map +1 -0
- package/lib/typescript/ble/deviceLogs.d.ts +10 -0
- package/lib/typescript/ble/deviceLogs.d.ts.map +1 -0
- package/lib/typescript/cache/DeviceStateCache.d.ts +14 -0
- package/lib/typescript/cache/DeviceStateCache.d.ts.map +1 -0
- package/lib/typescript/client.d.ts +208 -0
- package/lib/typescript/client.d.ts.map +1 -0
- package/lib/typescript/compatibility/runtime.d.ts +11 -0
- package/lib/typescript/compatibility/runtime.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +19 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/managers/DeviceManager.d.ts +117 -0
- package/lib/typescript/managers/DeviceManager.d.ts.map +1 -0
- package/lib/typescript/managers/OTAManager.d.ts +25 -0
- package/lib/typescript/managers/OTAManager.d.ts.map +1 -0
- package/lib/typescript/managers/RecordingManager.d.ts +54 -0
- package/lib/typescript/managers/RecordingManager.d.ts.map +1 -0
- package/lib/typescript/managers/StreamingSession.d.ts +35 -0
- package/lib/typescript/managers/StreamingSession.d.ts.map +1 -0
- package/lib/typescript/managers/types.d.ts +19 -0
- package/lib/typescript/managers/types.d.ts.map +1 -0
- package/lib/typescript/models/Device.d.ts +392 -0
- package/lib/typescript/models/Device.d.ts.map +1 -0
- package/lib/typescript/models/Recording.d.ts +259 -0
- package/lib/typescript/models/Recording.d.ts.map +1 -0
- package/lib/typescript/models/Status.d.ts +106 -0
- package/lib/typescript/models/Status.d.ts.map +1 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +393 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -0
- package/lib/typescript/sync/syncStatus.d.ts +123 -0
- package/lib/typescript/sync/syncStatus.d.ts.map +1 -0
- package/lib/typescript/utils/errors.d.ts +83 -0
- package/lib/typescript/utils/errors.d.ts.map +1 -0
- package/lib/typescript/utils/logger.d.ts +36 -0
- package/lib/typescript/utils/logger.d.ts.map +1 -0
- package/package.json +100 -0
- package/scripts/bota_device_sdk_spm_workaround.rb +53 -0
- package/src/BotaClient.ts +231 -0
- package/src/ble/constants.ts +3 -0
- package/src/ble/deviceLogs.ts +61 -0
- package/src/cache/DeviceStateCache.ts +20 -0
- package/src/client.ts +1410 -0
- package/src/compatibility/runtime.ts +55 -0
- package/src/index.ts +146 -0
- package/src/managers/DeviceManager.ts +927 -0
- package/src/managers/OTAManager.ts +195 -0
- package/src/managers/RecordingManager.ts +578 -0
- package/src/managers/StreamingSession.ts +212 -0
- package/src/managers/types.ts +36 -0
- package/src/models/Device.ts +484 -0
- package/src/models/Recording.ts +281 -0
- package/src/models/Status.ts +128 -0
- package/src/specs/NativeBotaDeviceSDK.ts +542 -0
- package/src/sync/syncStatus.ts +243 -0
- package/src/utils/errors.ts +327 -0
- package/src/utils/logger.ts +122 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import BotaAppSDK
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
struct BotaDeviceSDKAppleProvisioningRequest: Equatable, Sendable {
|
|
5
|
+
let requestID: String
|
|
6
|
+
let serialNumber: String
|
|
7
|
+
let nonce: String
|
|
8
|
+
let devicePublicKey: String
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct BotaDeviceSDKAppleFactoryResetRequest: Equatable, Sendable {
|
|
12
|
+
let requestID: String
|
|
13
|
+
let serialNumber: String
|
|
14
|
+
let nonce: String
|
|
15
|
+
let commandID: String
|
|
16
|
+
let bindingGeneration: UInt64
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
struct BotaDeviceSDKAppleFactoryResetPersistenceRequest: Equatable, Sendable {
|
|
20
|
+
let requestID: String
|
|
21
|
+
let localRecordingsDeleted: UInt16
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protocol BotaDeviceSDKAppleSecurityClient: Sendable {
|
|
25
|
+
func isProvisioned(_ device: ConnectedDevice) async throws -> Bool
|
|
26
|
+
func readPublicKey(from device: ConnectedDevice) async throws -> String?
|
|
27
|
+
func readAuthNonce(from device: ConnectedDevice) async throws -> String?
|
|
28
|
+
func setAPIEndpoint(_ environment: DeviceAPIEnvironment, on device: ConnectedDevice) async throws
|
|
29
|
+
func deliverCertificate(
|
|
30
|
+
_ certificatePEM: String,
|
|
31
|
+
privateKeyPEM: String,
|
|
32
|
+
to device: ConnectedDevice
|
|
33
|
+
) async throws
|
|
34
|
+
func deliverBackendPublicKey(_ publicKey: Data, to device: ConnectedDevice) async throws
|
|
35
|
+
func writeGrant(_ grantBlob: String, to device: ConnectedDevice) async throws
|
|
36
|
+
func syncTime(_ device: ConnectedDevice) async throws
|
|
37
|
+
func requestStartRecording(
|
|
38
|
+
_ device: ConnectedDevice,
|
|
39
|
+
grantBlob: String
|
|
40
|
+
) async throws -> RecordingControlResult
|
|
41
|
+
func requestStopRecording(
|
|
42
|
+
_ device: ConnectedDevice,
|
|
43
|
+
grantBlob: String
|
|
44
|
+
) async throws -> RecordingControlResult
|
|
45
|
+
func readRecordingState(from device: ConnectedDevice) async throws -> RecordingState
|
|
46
|
+
func recordingStateUpdates(
|
|
47
|
+
_ device: ConnectedDevice
|
|
48
|
+
) async throws -> AsyncThrowingStream<RecordingState, Error>
|
|
49
|
+
func provision(
|
|
50
|
+
_ device: ConnectedDevice,
|
|
51
|
+
using provider: @escaping ProvisioningMaterialProvider
|
|
52
|
+
) async throws
|
|
53
|
+
func deprovision(
|
|
54
|
+
_ device: ConnectedDevice,
|
|
55
|
+
grantBlob: String
|
|
56
|
+
) async throws -> DeprovisionResult
|
|
57
|
+
func readConnectionSettings(from device: ConnectedDevice) async throws -> DeviceConnectionSettings
|
|
58
|
+
func writeConnectionSettings(
|
|
59
|
+
_ settings: DeviceConnectionSettings,
|
|
60
|
+
to device: ConnectedDevice
|
|
61
|
+
) async throws
|
|
62
|
+
func factoryReset(
|
|
63
|
+
_ device: ConnectedDevice,
|
|
64
|
+
commandID: String,
|
|
65
|
+
bindingGeneration: UInt64,
|
|
66
|
+
persistResult: FactoryResetResultPersister?,
|
|
67
|
+
using provider: @escaping FactoryResetGrantProvider
|
|
68
|
+
) async throws -> FactoryResetCompletion
|
|
69
|
+
func resumePendingFactoryReset(
|
|
70
|
+
_ device: ConnectedDevice,
|
|
71
|
+
currentBindingGeneration: UInt64,
|
|
72
|
+
persistResult: FactoryResetResultPersister?
|
|
73
|
+
) async throws -> FactoryResetCompletion?
|
|
74
|
+
func resumeUnjournaledFactoryReset(
|
|
75
|
+
_ device: ConnectedDevice,
|
|
76
|
+
commandID: String,
|
|
77
|
+
bindingGeneration: UInt64,
|
|
78
|
+
persistResult: @escaping FactoryResetResultPersister
|
|
79
|
+
) async throws -> FactoryResetCompletion
|
|
80
|
+
func cancelCurrentOperation() async throws
|
|
81
|
+
func cancelFactoryReset() async throws
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
struct BotaDeviceSDKSharedAppleSecurityClient: BotaDeviceSDKAppleSecurityClient {
|
|
85
|
+
private let controls: DeviceControlManager
|
|
86
|
+
private let provisioning: ProvisioningManager
|
|
87
|
+
private let factoryResetManager: FactoryResetManager
|
|
88
|
+
|
|
89
|
+
init(client: BotaDeviceClient = .shared) {
|
|
90
|
+
controls = client.controls
|
|
91
|
+
provisioning = client.provisioning
|
|
92
|
+
factoryResetManager = client.factoryReset
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func isProvisioned(_ device: ConnectedDevice) async throws -> Bool {
|
|
96
|
+
try await controls.isProvisioned(device)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func readPublicKey(from device: ConnectedDevice) async throws -> String? {
|
|
100
|
+
try await controls.readPublicKey(from: device)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func readAuthNonce(from device: ConnectedDevice) async throws -> String? {
|
|
104
|
+
try await controls.readAuthNonce(from: device)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func setAPIEndpoint(_ environment: DeviceAPIEnvironment, on device: ConnectedDevice) async throws {
|
|
108
|
+
try await controls.setAPIEndpoint(environment, on: device)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func deliverCertificate(
|
|
112
|
+
_ certificatePEM: String,
|
|
113
|
+
privateKeyPEM: String,
|
|
114
|
+
to device: ConnectedDevice
|
|
115
|
+
) async throws {
|
|
116
|
+
try await controls.deliverCertificate(
|
|
117
|
+
certificatePEM,
|
|
118
|
+
privateKeyPEM: privateKeyPEM,
|
|
119
|
+
to: device
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func deliverBackendPublicKey(_ publicKey: Data, to device: ConnectedDevice) async throws {
|
|
124
|
+
try await controls.deliverBackendPublicKey(publicKey, to: device)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func writeGrant(_ grantBlob: String, to device: ConnectedDevice) async throws {
|
|
128
|
+
try await controls.writeGrant(grantBlob, to: device)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func syncTime(_ device: ConnectedDevice) async throws {
|
|
132
|
+
try await controls.syncTime(to: device)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func requestStartRecording(
|
|
136
|
+
_ device: ConnectedDevice,
|
|
137
|
+
grantBlob: String
|
|
138
|
+
) async throws -> RecordingControlResult {
|
|
139
|
+
try await controls.requestStartRecording(device, grantBlob: grantBlob)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func requestStopRecording(
|
|
143
|
+
_ device: ConnectedDevice,
|
|
144
|
+
grantBlob: String
|
|
145
|
+
) async throws -> RecordingControlResult {
|
|
146
|
+
try await controls.requestStopRecording(device, grantBlob: grantBlob)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func readRecordingState(from device: ConnectedDevice) async throws -> RecordingState {
|
|
150
|
+
try await controls.readRecordingState(from: device)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func recordingStateUpdates(
|
|
154
|
+
_ device: ConnectedDevice
|
|
155
|
+
) async throws -> AsyncThrowingStream<RecordingState, Error> {
|
|
156
|
+
try await controls.recordingStateUpdates(for: device)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
func provision(
|
|
160
|
+
_ device: ConnectedDevice,
|
|
161
|
+
using provider: @escaping ProvisioningMaterialProvider
|
|
162
|
+
) async throws {
|
|
163
|
+
try await provisioning.provision(
|
|
164
|
+
device,
|
|
165
|
+
materialID: UUID().uuidString,
|
|
166
|
+
using: provider
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func deprovision(
|
|
171
|
+
_ device: ConnectedDevice,
|
|
172
|
+
grantBlob: String
|
|
173
|
+
) async throws -> DeprovisionResult {
|
|
174
|
+
try await provisioning.deprovision(device, grantBlob: grantBlob)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func readConnectionSettings(from device: ConnectedDevice) async throws -> DeviceConnectionSettings {
|
|
178
|
+
try await provisioning.readConnectionSettings(from: device)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func writeConnectionSettings(
|
|
182
|
+
_ settings: DeviceConnectionSettings,
|
|
183
|
+
to device: ConnectedDevice
|
|
184
|
+
) async throws {
|
|
185
|
+
try await provisioning.writeConnectionSettings(settings, to: device)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func factoryReset(
|
|
189
|
+
_ device: ConnectedDevice,
|
|
190
|
+
commandID: String,
|
|
191
|
+
bindingGeneration: UInt64,
|
|
192
|
+
persistResult: FactoryResetResultPersister?,
|
|
193
|
+
using provider: @escaping FactoryResetGrantProvider
|
|
194
|
+
) async throws -> FactoryResetCompletion {
|
|
195
|
+
try await factoryResetManager.factoryReset(
|
|
196
|
+
device,
|
|
197
|
+
commandID: commandID,
|
|
198
|
+
grantID: UUID().uuidString,
|
|
199
|
+
bindingGeneration: bindingGeneration,
|
|
200
|
+
persistResult: persistResult,
|
|
201
|
+
using: provider
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func resumePendingFactoryReset(
|
|
206
|
+
_ device: ConnectedDevice,
|
|
207
|
+
currentBindingGeneration: UInt64,
|
|
208
|
+
persistResult: FactoryResetResultPersister?
|
|
209
|
+
) async throws -> FactoryResetCompletion? {
|
|
210
|
+
try await factoryResetManager.resumePendingFactoryReset(
|
|
211
|
+
device,
|
|
212
|
+
currentBindingGeneration: currentBindingGeneration,
|
|
213
|
+
persistResult: persistResult
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
func resumeUnjournaledFactoryReset(
|
|
218
|
+
_ device: ConnectedDevice,
|
|
219
|
+
commandID: String,
|
|
220
|
+
bindingGeneration: UInt64,
|
|
221
|
+
persistResult: @escaping FactoryResetResultPersister
|
|
222
|
+
) async throws -> FactoryResetCompletion {
|
|
223
|
+
try await factoryResetManager.resumeUnjournaledFactoryReset(
|
|
224
|
+
device,
|
|
225
|
+
commandID: commandID,
|
|
226
|
+
bindingGeneration: bindingGeneration,
|
|
227
|
+
persistResult: persistResult
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
func cancelCurrentOperation() async throws {
|
|
232
|
+
try await provisioning.cancelCurrentOperation()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
func cancelFactoryReset() async throws {
|
|
236
|
+
try await factoryResetManager.cancelCurrentOperation()
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
actor BotaDeviceSDKAppleSecurity {
|
|
241
|
+
private enum MaterialError: LocalizedError {
|
|
242
|
+
case cancelled
|
|
243
|
+
case invalidFactoryResetGrant
|
|
244
|
+
case rejected(String)
|
|
245
|
+
case unknownRequest
|
|
246
|
+
|
|
247
|
+
var errorDescription: String? {
|
|
248
|
+
switch self {
|
|
249
|
+
case .cancelled: "application material request was cancelled"
|
|
250
|
+
case .invalidFactoryResetGrant: "factory reset grant is not valid encoded data"
|
|
251
|
+
case let .rejected(message): message
|
|
252
|
+
case .unknownRequest: "application material request is no longer pending"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private let client: any BotaDeviceSDKAppleSecurityClient
|
|
258
|
+
private var provisioningRequests: [
|
|
259
|
+
String: CheckedContinuation<ProvisioningMaterial, Error>
|
|
260
|
+
] = [:]
|
|
261
|
+
private var factoryResetRequests: [String: CheckedContinuation<Data, Error>] = [:]
|
|
262
|
+
private var factoryResetPersistenceRequests: [String: CheckedContinuation<Void, Error>] = [:]
|
|
263
|
+
private var recordingStateTask: Task<Void, Never>?
|
|
264
|
+
|
|
265
|
+
init(client: any BotaDeviceSDKAppleSecurityClient = BotaDeviceSDKSharedAppleSecurityClient()) {
|
|
266
|
+
self.client = client
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func provision(
|
|
270
|
+
_ device: ConnectedDevice,
|
|
271
|
+
onMaterialRequest: @escaping @Sendable (BotaDeviceSDKAppleProvisioningRequest) -> Void
|
|
272
|
+
) async throws {
|
|
273
|
+
try await client.provision(device) { request in
|
|
274
|
+
try await self.requestProvisioningMaterial(request, onRequest: onMaterialRequest)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
func deprovision(
|
|
279
|
+
_ device: ConnectedDevice,
|
|
280
|
+
grantBlob: String
|
|
281
|
+
) async throws -> DeprovisionResult {
|
|
282
|
+
try await client.deprovision(device, grantBlob: grantBlob)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
func isProvisioned(_ device: ConnectedDevice) async throws -> Bool {
|
|
286
|
+
try await client.isProvisioned(device)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func readPublicKey(from device: ConnectedDevice) async throws -> String? {
|
|
290
|
+
try await client.readPublicKey(from: device)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func readAuthNonce(from device: ConnectedDevice) async throws -> String? {
|
|
294
|
+
try await client.readAuthNonce(from: device)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
func setAPIEndpoint(_ environment: DeviceAPIEnvironment, on device: ConnectedDevice) async throws {
|
|
298
|
+
try await client.setAPIEndpoint(environment, on: device)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
func deliverCertificate(
|
|
302
|
+
_ certificatePEM: String,
|
|
303
|
+
privateKeyPEM: String,
|
|
304
|
+
to device: ConnectedDevice
|
|
305
|
+
) async throws {
|
|
306
|
+
try await client.deliverCertificate(
|
|
307
|
+
certificatePEM,
|
|
308
|
+
privateKeyPEM: privateKeyPEM,
|
|
309
|
+
to: device
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
func deliverBackendPublicKey(_ publicKey: Data, to device: ConnectedDevice) async throws {
|
|
314
|
+
try await client.deliverBackendPublicKey(publicKey, to: device)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
func writeGrant(_ grantBlob: String, to device: ConnectedDevice) async throws {
|
|
318
|
+
try await client.writeGrant(grantBlob, to: device)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
func syncTime(_ device: ConnectedDevice) async throws {
|
|
322
|
+
try await client.syncTime(device)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
func requestStartRecording(
|
|
326
|
+
_ device: ConnectedDevice,
|
|
327
|
+
grantBlob: String
|
|
328
|
+
) async throws -> RecordingControlResult {
|
|
329
|
+
try await client.requestStartRecording(device, grantBlob: grantBlob)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func requestStopRecording(
|
|
333
|
+
_ device: ConnectedDevice,
|
|
334
|
+
grantBlob: String
|
|
335
|
+
) async throws -> RecordingControlResult {
|
|
336
|
+
try await client.requestStopRecording(device, grantBlob: grantBlob)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
func readRecordingState(from device: ConnectedDevice) async throws -> RecordingState {
|
|
340
|
+
try await client.readRecordingState(from: device)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
func startRecordingStateUpdates(
|
|
344
|
+
_ device: ConnectedDevice,
|
|
345
|
+
onState: @escaping @Sendable (RecordingState) -> Void,
|
|
346
|
+
onError: @escaping @Sendable (Error) -> Void = { _ in }
|
|
347
|
+
) async throws {
|
|
348
|
+
await stopRecordingStateUpdates()
|
|
349
|
+
let updates = try await client.recordingStateUpdates(device)
|
|
350
|
+
let task = Task {
|
|
351
|
+
do {
|
|
352
|
+
for try await state in updates { onState(state) }
|
|
353
|
+
} catch is CancellationError {
|
|
354
|
+
// Explicit stop is not a recording-state stream failure.
|
|
355
|
+
} catch {
|
|
356
|
+
onError(error)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
recordingStateTask = task
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
func stopRecordingStateUpdates() async {
|
|
363
|
+
guard let task = recordingStateTask else { return }
|
|
364
|
+
recordingStateTask = nil
|
|
365
|
+
task.cancel()
|
|
366
|
+
await task.value
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
func readConnectionSettings(from device: ConnectedDevice) async throws -> DeviceConnectionSettings {
|
|
370
|
+
try await client.readConnectionSettings(from: device)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
func writeConnectionSettings(
|
|
374
|
+
_ settings: DeviceConnectionSettings,
|
|
375
|
+
to device: ConnectedDevice
|
|
376
|
+
) async throws {
|
|
377
|
+
try await client.writeConnectionSettings(settings, to: device)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
func factoryReset(
|
|
381
|
+
_ device: ConnectedDevice,
|
|
382
|
+
commandID: String,
|
|
383
|
+
bindingGeneration: UInt64,
|
|
384
|
+
onGrantRequest: @escaping @Sendable (BotaDeviceSDKAppleFactoryResetRequest) -> Void,
|
|
385
|
+
onPersistenceRequest: (@Sendable (
|
|
386
|
+
BotaDeviceSDKAppleFactoryResetPersistenceRequest
|
|
387
|
+
) -> Void)?
|
|
388
|
+
) async throws -> FactoryResetCompletion {
|
|
389
|
+
let persister: FactoryResetResultPersister?
|
|
390
|
+
if let onPersistenceRequest {
|
|
391
|
+
persister = { result in
|
|
392
|
+
try await self.requestFactoryResetPersistence(
|
|
393
|
+
result,
|
|
394
|
+
onRequest: onPersistenceRequest
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
persister = nil
|
|
399
|
+
}
|
|
400
|
+
return try await client.factoryReset(
|
|
401
|
+
device,
|
|
402
|
+
commandID: commandID,
|
|
403
|
+
bindingGeneration: bindingGeneration,
|
|
404
|
+
persistResult: persister
|
|
405
|
+
) { request in
|
|
406
|
+
try await self.requestFactoryResetGrant(request, onRequest: onGrantRequest)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
func resumePendingFactoryReset(
|
|
411
|
+
_ device: ConnectedDevice,
|
|
412
|
+
currentBindingGeneration: UInt64,
|
|
413
|
+
onPersistenceRequest: (@Sendable (
|
|
414
|
+
BotaDeviceSDKAppleFactoryResetPersistenceRequest
|
|
415
|
+
) -> Void)?
|
|
416
|
+
) async throws -> FactoryResetCompletion? {
|
|
417
|
+
let persister: FactoryResetResultPersister?
|
|
418
|
+
if let onPersistenceRequest {
|
|
419
|
+
persister = { result in
|
|
420
|
+
try await self.requestFactoryResetPersistence(
|
|
421
|
+
result,
|
|
422
|
+
onRequest: onPersistenceRequest
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
persister = nil
|
|
427
|
+
}
|
|
428
|
+
let pending = try await client.resumePendingFactoryReset(
|
|
429
|
+
device,
|
|
430
|
+
currentBindingGeneration: currentBindingGeneration,
|
|
431
|
+
persistResult: persister
|
|
432
|
+
)
|
|
433
|
+
if let pending { return pending }
|
|
434
|
+
guard let persister else { return nil }
|
|
435
|
+
return try await client.resumeUnjournaledFactoryReset(
|
|
436
|
+
device,
|
|
437
|
+
commandID: "rn-resume-\(UUID().uuidString)",
|
|
438
|
+
bindingGeneration: currentBindingGeneration,
|
|
439
|
+
persistResult: persister
|
|
440
|
+
)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
func resolveProvisioningMaterial(
|
|
444
|
+
requestID: String,
|
|
445
|
+
apiEndpoint: String,
|
|
446
|
+
deviceToken: String,
|
|
447
|
+
mtu: UInt64
|
|
448
|
+
) throws {
|
|
449
|
+
guard let continuation = provisioningRequests.removeValue(forKey: requestID) else {
|
|
450
|
+
throw MaterialError.unknownRequest
|
|
451
|
+
}
|
|
452
|
+
continuation.resume(returning: ProvisioningMaterial(
|
|
453
|
+
apiEndpoint: Data(apiEndpoint.utf8),
|
|
454
|
+
deviceToken: Data(deviceToken.utf8),
|
|
455
|
+
mtu: mtu
|
|
456
|
+
))
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
func rejectApplicationMaterial(requestID: String, message: String) throws {
|
|
460
|
+
if let continuation = provisioningRequests.removeValue(forKey: requestID) {
|
|
461
|
+
continuation.resume(throwing: MaterialError.rejected(message))
|
|
462
|
+
return
|
|
463
|
+
}
|
|
464
|
+
if let continuation = factoryResetRequests.removeValue(forKey: requestID) {
|
|
465
|
+
continuation.resume(throwing: MaterialError.rejected(message))
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
if let continuation = factoryResetPersistenceRequests.removeValue(forKey: requestID) {
|
|
469
|
+
continuation.resume(throwing: MaterialError.rejected(message))
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
throw MaterialError.unknownRequest
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
func resolveFactoryResetGrant(requestID: String, grantBlob: String) throws {
|
|
476
|
+
guard let continuation = factoryResetRequests.removeValue(forKey: requestID) else {
|
|
477
|
+
throw MaterialError.unknownRequest
|
|
478
|
+
}
|
|
479
|
+
guard let grant = Data(base64Encoded: grantBlob), !grant.isEmpty else {
|
|
480
|
+
continuation.resume(throwing: MaterialError.invalidFactoryResetGrant)
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
continuation.resume(returning: grant)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
func resolveFactoryResetResultPersistence(requestID: String) throws {
|
|
487
|
+
guard let continuation = factoryResetPersistenceRequests.removeValue(forKey: requestID) else {
|
|
488
|
+
throw MaterialError.unknownRequest
|
|
489
|
+
}
|
|
490
|
+
continuation.resume()
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
func cancelAll() async {
|
|
494
|
+
await stopRecordingStateUpdates()
|
|
495
|
+
let pending = provisioningRequests.values
|
|
496
|
+
provisioningRequests.removeAll()
|
|
497
|
+
pending.forEach { $0.resume(throwing: MaterialError.cancelled) }
|
|
498
|
+
let pendingResets = factoryResetRequests.values
|
|
499
|
+
factoryResetRequests.removeAll()
|
|
500
|
+
pendingResets.forEach { $0.resume(throwing: MaterialError.cancelled) }
|
|
501
|
+
let pendingPersistence = factoryResetPersistenceRequests.values
|
|
502
|
+
factoryResetPersistenceRequests.removeAll()
|
|
503
|
+
pendingPersistence.forEach { $0.resume(throwing: MaterialError.cancelled) }
|
|
504
|
+
try? await client.cancelCurrentOperation()
|
|
505
|
+
try? await client.cancelFactoryReset()
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private func requestProvisioningMaterial(
|
|
509
|
+
_ request: ProvisioningMaterialRequest,
|
|
510
|
+
onRequest: @escaping @Sendable (BotaDeviceSDKAppleProvisioningRequest) -> Void
|
|
511
|
+
) async throws -> ProvisioningMaterial {
|
|
512
|
+
let requestID = UUID().uuidString
|
|
513
|
+
return try await withTaskCancellationHandler {
|
|
514
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
515
|
+
provisioningRequests[requestID] = continuation
|
|
516
|
+
onRequest(.init(
|
|
517
|
+
requestID: requestID,
|
|
518
|
+
serialNumber: request.serialNumber,
|
|
519
|
+
nonce: request.nonce.hexString,
|
|
520
|
+
devicePublicKey: request.devicePublicKey.hexString
|
|
521
|
+
))
|
|
522
|
+
}
|
|
523
|
+
} onCancel: {
|
|
524
|
+
Task { await self.cancel(requestID: requestID) }
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private func cancel(requestID: String) {
|
|
529
|
+
if let continuation = provisioningRequests.removeValue(forKey: requestID) {
|
|
530
|
+
continuation.resume(throwing: MaterialError.cancelled)
|
|
531
|
+
}
|
|
532
|
+
if let continuation = factoryResetRequests.removeValue(forKey: requestID) {
|
|
533
|
+
continuation.resume(throwing: MaterialError.cancelled)
|
|
534
|
+
}
|
|
535
|
+
if let continuation = factoryResetPersistenceRequests.removeValue(forKey: requestID) {
|
|
536
|
+
continuation.resume(throwing: MaterialError.cancelled)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private func requestFactoryResetGrant(
|
|
541
|
+
_ request: FactoryResetGrantRequest,
|
|
542
|
+
onRequest: @escaping @Sendable (BotaDeviceSDKAppleFactoryResetRequest) -> Void
|
|
543
|
+
) async throws -> Data {
|
|
544
|
+
let requestID = UUID().uuidString
|
|
545
|
+
return try await withTaskCancellationHandler {
|
|
546
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
547
|
+
factoryResetRequests[requestID] = continuation
|
|
548
|
+
onRequest(.init(
|
|
549
|
+
requestID: requestID,
|
|
550
|
+
serialNumber: request.serialNumber,
|
|
551
|
+
nonce: request.nonce.hexString,
|
|
552
|
+
commandID: request.commandID,
|
|
553
|
+
bindingGeneration: request.bindingGeneration
|
|
554
|
+
))
|
|
555
|
+
}
|
|
556
|
+
} onCancel: {
|
|
557
|
+
Task { await self.cancel(requestID: requestID) }
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private func requestFactoryResetPersistence(
|
|
562
|
+
_ result: FactoryResetPersistenceResult,
|
|
563
|
+
onRequest: @escaping @Sendable (
|
|
564
|
+
BotaDeviceSDKAppleFactoryResetPersistenceRequest
|
|
565
|
+
) -> Void
|
|
566
|
+
) async throws {
|
|
567
|
+
let requestID = UUID().uuidString
|
|
568
|
+
try await withTaskCancellationHandler {
|
|
569
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
570
|
+
factoryResetPersistenceRequests[requestID] = continuation
|
|
571
|
+
onRequest(.init(
|
|
572
|
+
requestID: requestID,
|
|
573
|
+
localRecordingsDeleted: result.localRecordingsDeleted
|
|
574
|
+
))
|
|
575
|
+
}
|
|
576
|
+
} onCancel: {
|
|
577
|
+
Task { await self.cancel(requestID: requestID) }
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private extension Data {
|
|
583
|
+
var hexString: String {
|
|
584
|
+
map { String(format: "%02x", $0) }.joined()
|
|
585
|
+
}
|
|
586
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import BotaAppSDK
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
protocol BotaDeviceSDKAppleWiFiClient: Sendable {
|
|
5
|
+
func configure(
|
|
6
|
+
_ device: ConnectedDevice,
|
|
7
|
+
ssid: String,
|
|
8
|
+
password: String,
|
|
9
|
+
grantBlob: String
|
|
10
|
+
) async throws -> WiFiConfigResult
|
|
11
|
+
func disconnect(_ device: ConnectedDevice) async throws -> WiFiConfigResult
|
|
12
|
+
func readStatus(_ device: ConnectedDevice) async throws -> WiFiStatusInfo
|
|
13
|
+
func statusUpdates(
|
|
14
|
+
_ device: ConnectedDevice
|
|
15
|
+
) async throws -> AsyncThrowingStream<WiFiStatusInfo, Error>
|
|
16
|
+
func scanNetworks(_ device: ConnectedDevice) async throws -> DeviceWiFiScanResult
|
|
17
|
+
func cancelCurrentOperation() async
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct BotaDeviceSDKSharedAppleWiFiClient: BotaDeviceSDKAppleWiFiClient {
|
|
21
|
+
private let wifi: WiFiManager
|
|
22
|
+
|
|
23
|
+
init(client: BotaDeviceClient = .shared) {
|
|
24
|
+
wifi = client.wifi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func configure(
|
|
28
|
+
_ device: ConnectedDevice,
|
|
29
|
+
ssid: String,
|
|
30
|
+
password: String,
|
|
31
|
+
grantBlob: String
|
|
32
|
+
) async throws -> WiFiConfigResult {
|
|
33
|
+
try await wifi.configure(
|
|
34
|
+
device,
|
|
35
|
+
ssid: ssid,
|
|
36
|
+
password: password,
|
|
37
|
+
grantBlob: grantBlob
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func disconnect(_ device: ConnectedDevice) async throws -> WiFiConfigResult {
|
|
42
|
+
try await wifi.disconnect(device)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func readStatus(_ device: ConnectedDevice) async throws -> WiFiStatusInfo {
|
|
46
|
+
try await wifi.readStatus(device)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func statusUpdates(
|
|
50
|
+
_ device: ConnectedDevice
|
|
51
|
+
) async throws -> AsyncThrowingStream<WiFiStatusInfo, Error> {
|
|
52
|
+
try await wifi.statusUpdates(device)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func scanNetworks(_ device: ConnectedDevice) async throws -> DeviceWiFiScanResult {
|
|
56
|
+
try await wifi.scanNetworks(device)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func cancelCurrentOperation() async {
|
|
60
|
+
await wifi.cancelCurrentOperation()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
actor BotaDeviceSDKAppleWiFi {
|
|
65
|
+
private let client: any BotaDeviceSDKAppleWiFiClient
|
|
66
|
+
private var statusTask: Task<Void, Never>?
|
|
67
|
+
|
|
68
|
+
init(client: any BotaDeviceSDKAppleWiFiClient = BotaDeviceSDKSharedAppleWiFiClient()) {
|
|
69
|
+
self.client = client
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func configure(
|
|
73
|
+
_ device: ConnectedDevice,
|
|
74
|
+
ssid: String,
|
|
75
|
+
password: String,
|
|
76
|
+
grantBlob: String
|
|
77
|
+
) async throws -> WiFiConfigResult {
|
|
78
|
+
try await client.configure(
|
|
79
|
+
device,
|
|
80
|
+
ssid: ssid,
|
|
81
|
+
password: password,
|
|
82
|
+
grantBlob: grantBlob
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func disconnect(_ device: ConnectedDevice) async throws -> WiFiConfigResult {
|
|
87
|
+
try await client.disconnect(device)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
func readStatus(_ device: ConnectedDevice) async throws -> WiFiStatusInfo {
|
|
91
|
+
try await client.readStatus(device)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func scanNetworks(_ device: ConnectedDevice) async throws -> DeviceWiFiScanResult {
|
|
95
|
+
try await client.scanNetworks(device)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func startStatusUpdates(
|
|
99
|
+
_ device: ConnectedDevice,
|
|
100
|
+
onStatus: @escaping @Sendable (WiFiStatusInfo) -> Void,
|
|
101
|
+
onError: @escaping @Sendable (Error) -> Void = { _ in }
|
|
102
|
+
) async throws {
|
|
103
|
+
await stopStatusUpdates()
|
|
104
|
+
let updates = try await client.statusUpdates(device)
|
|
105
|
+
let task = Task {
|
|
106
|
+
do {
|
|
107
|
+
for try await status in updates { onStatus(status) }
|
|
108
|
+
} catch is CancellationError {
|
|
109
|
+
// Explicit stop is not a status-stream failure.
|
|
110
|
+
} catch {
|
|
111
|
+
onError(error)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
statusTask = task
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func stopStatusUpdates() async {
|
|
118
|
+
guard let task = statusTask else { return }
|
|
119
|
+
statusTask = nil
|
|
120
|
+
task.cancel()
|
|
121
|
+
await task.value
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func cancelAll() async {
|
|
125
|
+
await stopStatusUpdates()
|
|
126
|
+
await client.cancelCurrentOperation()
|
|
127
|
+
}
|
|
128
|
+
}
|