@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.
Files changed (173) hide show
  1. package/BotaDeviceSDK.podspec +55 -0
  2. package/LICENSE +21 -0
  3. package/README.md +101 -0
  4. package/android/build.gradle +76 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +172 -0
  7. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycle.kt +100 -0
  8. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +82 -0
  9. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidModels.kt +476 -0
  10. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTA.kt +58 -0
  11. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +180 -0
  12. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +751 -0
  13. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurity.kt +509 -0
  14. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFi.kt +125 -0
  15. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +46 -0
  16. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +723 -0
  17. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKPackage.kt +33 -0
  18. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +211 -0
  19. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycleTest.kt +132 -0
  20. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +57 -0
  21. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTATest.kt +75 -0
  22. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +147 -0
  23. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +355 -0
  24. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurityTest.kt +523 -0
  25. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFiTest.kt +100 -0
  26. package/generated/codegen-contract.json +3593 -0
  27. package/ios/BotaDeviceSDK.mm +1300 -0
  28. package/ios/BotaDeviceSDKAppleBridge.swift +2260 -0
  29. package/ios/BotaDeviceSDKAppleDevices.swift +191 -0
  30. package/ios/BotaDeviceSDKAppleLifecycle.swift +167 -0
  31. package/ios/BotaDeviceSDKAppleLogs.swift +79 -0
  32. package/ios/BotaDeviceSDKAppleOTA.swift +73 -0
  33. package/ios/BotaDeviceSDKAppleRecordingUploads.swift +178 -0
  34. package/ios/BotaDeviceSDKAppleRecordings.swift +670 -0
  35. package/ios/BotaDeviceSDKAppleSecurity.swift +586 -0
  36. package/ios/BotaDeviceSDKAppleWiFi.swift +128 -0
  37. package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +99 -0
  38. package/lib/commonjs/BotaClient.js +194 -0
  39. package/lib/commonjs/BotaClient.js.map +1 -0
  40. package/lib/commonjs/ble/constants.js +10 -0
  41. package/lib/commonjs/ble/constants.js.map +1 -0
  42. package/lib/commonjs/ble/deviceLogs.js +50 -0
  43. package/lib/commonjs/ble/deviceLogs.js.map +1 -0
  44. package/lib/commonjs/cache/DeviceStateCache.js +6 -0
  45. package/lib/commonjs/cache/DeviceStateCache.js.map +1 -0
  46. package/lib/commonjs/client.js +746 -0
  47. package/lib/commonjs/client.js.map +1 -0
  48. package/lib/commonjs/compatibility/runtime.js +44 -0
  49. package/lib/commonjs/compatibility/runtime.js.map +1 -0
  50. package/lib/commonjs/index.js +123 -0
  51. package/lib/commonjs/index.js.map +1 -0
  52. package/lib/commonjs/managers/DeviceManager.js +647 -0
  53. package/lib/commonjs/managers/DeviceManager.js.map +1 -0
  54. package/lib/commonjs/managers/OTAManager.js +140 -0
  55. package/lib/commonjs/managers/OTAManager.js.map +1 -0
  56. package/lib/commonjs/managers/RecordingManager.js +444 -0
  57. package/lib/commonjs/managers/RecordingManager.js.map +1 -0
  58. package/lib/commonjs/managers/StreamingSession.js +175 -0
  59. package/lib/commonjs/managers/StreamingSession.js.map +1 -0
  60. package/lib/commonjs/managers/types.js +6 -0
  61. package/lib/commonjs/managers/types.js.map +1 -0
  62. package/lib/commonjs/models/Device.js +2 -0
  63. package/lib/commonjs/models/Device.js.map +1 -0
  64. package/lib/commonjs/models/Recording.js +2 -0
  65. package/lib/commonjs/models/Recording.js.map +1 -0
  66. package/lib/commonjs/models/Status.js +6 -0
  67. package/lib/commonjs/models/Status.js.map +1 -0
  68. package/lib/commonjs/package.json +1 -0
  69. package/lib/commonjs/specs/NativeBotaDeviceSDK.js +9 -0
  70. package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -0
  71. package/lib/commonjs/sync/syncStatus.js +178 -0
  72. package/lib/commonjs/sync/syncStatus.js.map +1 -0
  73. package/lib/commonjs/utils/errors.js +194 -0
  74. package/lib/commonjs/utils/errors.js.map +1 -0
  75. package/lib/commonjs/utils/logger.js +84 -0
  76. package/lib/commonjs/utils/logger.js.map +1 -0
  77. package/lib/module/BotaClient.js +189 -0
  78. package/lib/module/BotaClient.js.map +1 -0
  79. package/lib/module/ble/constants.js +6 -0
  80. package/lib/module/ble/constants.js.map +1 -0
  81. package/lib/module/ble/deviceLogs.js +45 -0
  82. package/lib/module/ble/deviceLogs.js.map +1 -0
  83. package/lib/module/cache/DeviceStateCache.js +4 -0
  84. package/lib/module/cache/DeviceStateCache.js.map +1 -0
  85. package/lib/module/client.js +740 -0
  86. package/lib/module/client.js.map +1 -0
  87. package/lib/module/compatibility/runtime.js +35 -0
  88. package/lib/module/compatibility/runtime.js.map +1 -0
  89. package/lib/module/index.js +17 -0
  90. package/lib/module/index.js.map +1 -0
  91. package/lib/module/managers/DeviceManager.js +641 -0
  92. package/lib/module/managers/DeviceManager.js.map +1 -0
  93. package/lib/module/managers/OTAManager.js +134 -0
  94. package/lib/module/managers/OTAManager.js.map +1 -0
  95. package/lib/module/managers/RecordingManager.js +438 -0
  96. package/lib/module/managers/RecordingManager.js.map +1 -0
  97. package/lib/module/managers/StreamingSession.js +168 -0
  98. package/lib/module/managers/StreamingSession.js.map +1 -0
  99. package/lib/module/managers/types.js +4 -0
  100. package/lib/module/managers/types.js.map +1 -0
  101. package/lib/module/models/Device.js +2 -0
  102. package/lib/module/models/Device.js.map +1 -0
  103. package/lib/module/models/Recording.js +2 -0
  104. package/lib/module/models/Recording.js.map +1 -0
  105. package/lib/module/models/Status.js +4 -0
  106. package/lib/module/models/Status.js.map +1 -0
  107. package/lib/module/specs/NativeBotaDeviceSDK.js +5 -0
  108. package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -0
  109. package/lib/module/sync/syncStatus.js +174 -0
  110. package/lib/module/sync/syncStatus.js.map +1 -0
  111. package/lib/module/utils/errors.js +183 -0
  112. package/lib/module/utils/errors.js.map +1 -0
  113. package/lib/module/utils/logger.js +80 -0
  114. package/lib/module/utils/logger.js.map +1 -0
  115. package/lib/typescript/BotaClient.d.ts +40 -0
  116. package/lib/typescript/BotaClient.d.ts.map +1 -0
  117. package/lib/typescript/ble/constants.d.ts +4 -0
  118. package/lib/typescript/ble/constants.d.ts.map +1 -0
  119. package/lib/typescript/ble/deviceLogs.d.ts +10 -0
  120. package/lib/typescript/ble/deviceLogs.d.ts.map +1 -0
  121. package/lib/typescript/cache/DeviceStateCache.d.ts +14 -0
  122. package/lib/typescript/cache/DeviceStateCache.d.ts.map +1 -0
  123. package/lib/typescript/client.d.ts +208 -0
  124. package/lib/typescript/client.d.ts.map +1 -0
  125. package/lib/typescript/compatibility/runtime.d.ts +11 -0
  126. package/lib/typescript/compatibility/runtime.d.ts.map +1 -0
  127. package/lib/typescript/index.d.ts +19 -0
  128. package/lib/typescript/index.d.ts.map +1 -0
  129. package/lib/typescript/managers/DeviceManager.d.ts +117 -0
  130. package/lib/typescript/managers/DeviceManager.d.ts.map +1 -0
  131. package/lib/typescript/managers/OTAManager.d.ts +25 -0
  132. package/lib/typescript/managers/OTAManager.d.ts.map +1 -0
  133. package/lib/typescript/managers/RecordingManager.d.ts +54 -0
  134. package/lib/typescript/managers/RecordingManager.d.ts.map +1 -0
  135. package/lib/typescript/managers/StreamingSession.d.ts +35 -0
  136. package/lib/typescript/managers/StreamingSession.d.ts.map +1 -0
  137. package/lib/typescript/managers/types.d.ts +19 -0
  138. package/lib/typescript/managers/types.d.ts.map +1 -0
  139. package/lib/typescript/models/Device.d.ts +392 -0
  140. package/lib/typescript/models/Device.d.ts.map +1 -0
  141. package/lib/typescript/models/Recording.d.ts +259 -0
  142. package/lib/typescript/models/Recording.d.ts.map +1 -0
  143. package/lib/typescript/models/Status.d.ts +106 -0
  144. package/lib/typescript/models/Status.d.ts.map +1 -0
  145. package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +393 -0
  146. package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -0
  147. package/lib/typescript/sync/syncStatus.d.ts +123 -0
  148. package/lib/typescript/sync/syncStatus.d.ts.map +1 -0
  149. package/lib/typescript/utils/errors.d.ts +83 -0
  150. package/lib/typescript/utils/errors.d.ts.map +1 -0
  151. package/lib/typescript/utils/logger.d.ts +36 -0
  152. package/lib/typescript/utils/logger.d.ts.map +1 -0
  153. package/package.json +100 -0
  154. package/scripts/bota_device_sdk_spm_workaround.rb +53 -0
  155. package/src/BotaClient.ts +231 -0
  156. package/src/ble/constants.ts +3 -0
  157. package/src/ble/deviceLogs.ts +61 -0
  158. package/src/cache/DeviceStateCache.ts +20 -0
  159. package/src/client.ts +1410 -0
  160. package/src/compatibility/runtime.ts +55 -0
  161. package/src/index.ts +146 -0
  162. package/src/managers/DeviceManager.ts +927 -0
  163. package/src/managers/OTAManager.ts +195 -0
  164. package/src/managers/RecordingManager.ts +578 -0
  165. package/src/managers/StreamingSession.ts +212 -0
  166. package/src/managers/types.ts +36 -0
  167. package/src/models/Device.ts +484 -0
  168. package/src/models/Recording.ts +281 -0
  169. package/src/models/Status.ts +128 -0
  170. package/src/specs/NativeBotaDeviceSDK.ts +542 -0
  171. package/src/sync/syncStatus.ts +243 -0
  172. package/src/utils/errors.ts +327 -0
  173. 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
+ }