@bota.dev/react-native-app-sdk 2.0.0-beta.14 → 2.0.0-beta.15
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/README.md +36 -7
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +3 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/BotaUploadV2BackendModule.kt +37 -3
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/ByoBridgeDriver.kt +79 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/NativeUpload.kt +30 -2
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/UploadModels.kt +5 -2
- package/android/src/main/java/dev/bota/sdk/reactnative/upload/UploadStreamingStatus.kt +67 -0
- package/generated/codegen-contract.json +110 -8
- package/ios/BotaDeviceSDK.mm +1 -0
- package/ios/BotaDeviceSDKAppleBridge.swift +4 -2
- package/ios/BotaDeviceSDKAppleRecordings.swift +1 -0
- package/ios/BotaUploadV2Backend.mm +21 -0
- package/ios/UploadBackend/BotaUploadV2BackendBridge.swift +52 -2
- package/ios/UploadBackend/ByoBridgeDriver.swift +94 -0
- package/ios/UploadBackend/UploadModels.swift +6 -1
- package/ios/UploadBackend/UploadOperation.swift +41 -3
- package/ios/UploadBackend/UploadStreamingStatus.swift +43 -0
- package/lib/commonjs/byoStorageBackend.js +144 -0
- package/lib/commonjs/byoStorageBackend.js.map +1 -0
- package/lib/commonjs/client.js +2 -1
- package/lib/commonjs/client.js.map +1 -1
- package/lib/commonjs/encryptedUploadV2Backend.js +4 -1
- package/lib/commonjs/encryptedUploadV2Backend.js.map +1 -1
- package/lib/commonjs/index.js +7 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/managers/DeviceManager.js +6 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -1
- package/lib/commonjs/protectedStreamingStatus.js +120 -0
- package/lib/commonjs/protectedStreamingStatus.js.map +1 -0
- package/lib/commonjs/sdkIdentity.js +1 -1
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/commonjs/specs/NativeBotaUploadV2Backend.js.map +1 -1
- package/lib/module/byoStorageBackend.js +140 -0
- package/lib/module/byoStorageBackend.js.map +1 -0
- package/lib/module/client.js +2 -1
- package/lib/module/client.js.map +1 -1
- package/lib/module/encryptedUploadV2Backend.js +4 -1
- package/lib/module/encryptedUploadV2Backend.js.map +1 -1
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/managers/DeviceManager.js +6 -0
- package/lib/module/managers/DeviceManager.js.map +1 -1
- package/lib/module/protectedStreamingStatus.js +116 -0
- package/lib/module/protectedStreamingStatus.js.map +1 -0
- package/lib/module/sdkIdentity.js +1 -1
- package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/module/specs/NativeBotaUploadV2Backend.js.map +1 -1
- package/lib/typescript/byoStorageBackend.d.ts +36 -0
- package/lib/typescript/byoStorageBackend.d.ts.map +1 -0
- package/lib/typescript/client.d.ts.map +1 -1
- package/lib/typescript/encryptedUploadV2Backend.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/managers/DeviceManager.d.ts +2 -0
- package/lib/typescript/managers/DeviceManager.d.ts.map +1 -1
- package/lib/typescript/protectedStreamingStatus.d.ts +37 -0
- package/lib/typescript/protectedStreamingStatus.d.ts.map +1 -0
- package/lib/typescript/sdkIdentity.d.ts +1 -1
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +1 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -1
- package/lib/typescript/specs/NativeBotaUploadV2Backend.d.ts +4 -0
- package/lib/typescript/specs/NativeBotaUploadV2Backend.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/byoStorageBackend.ts +104 -0
- package/src/client.ts +2 -0
- package/src/encryptedUploadV2Backend.ts +2 -1
- package/src/index.ts +11 -0
- package/src/managers/DeviceManager.ts +6 -0
- package/src/protectedStreamingStatus.ts +137 -0
- package/src/sdkIdentity.ts +1 -1
- package/src/specs/NativeBotaDeviceSDK.ts +1 -0
- package/src/specs/NativeBotaUploadV2Backend.ts +4 -0
|
@@ -16,6 +16,15 @@ RCT_EXPORT_MODULE(BotaUploadV2Backend)
|
|
|
16
16
|
if ((self = [super init])) { _backend = [BotaUploadV2BackendBridge new]; }
|
|
17
17
|
return self;
|
|
18
18
|
}
|
|
19
|
+
- (void)readProtectedStreamingStatus:(NSString *)requestId inputJSON:(NSString *)inputJSON resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
20
|
+
[_backend readProtectedStreamingStatus:requestId inputJSON:inputJSON completion:^(NSString *result, NSError *error) {
|
|
21
|
+
if (error) { reject(@"BOTA_STREAM_STATUS_FAILED", @"BOTA_STREAM_STATUS_FAILED", nil); }
|
|
22
|
+
else { resolve(result); }
|
|
23
|
+
}];
|
|
24
|
+
}
|
|
25
|
+
- (void)cancelProtectedStreamingStatus:(NSString *)requestId resolve:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject {
|
|
26
|
+
[_backend cancelProtectedStreamingStatus:requestId completion:^{ resolve(nil); }];
|
|
27
|
+
}
|
|
19
28
|
- (void)prepare:(NSString *)inputJSON resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
20
29
|
__weak BotaUploadV2Backend *weakSelf = self;
|
|
21
30
|
[_backend prepare:inputJSON credentialsRequested:^(NSString *requestId, NSString *operationId) {
|
|
@@ -25,6 +34,18 @@ RCT_EXPORT_MODULE(BotaUploadV2Backend)
|
|
|
25
34
|
else { resolve(result); }
|
|
26
35
|
}];
|
|
27
36
|
}
|
|
37
|
+
- (void)syncByo:(NSString *)inputJSON resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
|
|
38
|
+
__weak BotaUploadV2Backend *weakSelf = self;
|
|
39
|
+
[_backend syncByo:inputJSON credentialsRequested:^(NSString *requestId, NSString *operationId) {
|
|
40
|
+
[weakSelf emitOnCredentialsRequested:@{@"requestId": requestId, @"operationId": operationId}];
|
|
41
|
+
} completion:^(NSString *result, NSError *error) {
|
|
42
|
+
if (error) { reject(error.userInfo[@"byoCode"] ?: @"byo_upload_failed", error.localizedDescription, nil); }
|
|
43
|
+
else { resolve(result); }
|
|
44
|
+
}];
|
|
45
|
+
}
|
|
46
|
+
- (void)cancelByo:(NSString *)operationId resolve:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject {
|
|
47
|
+
[_backend cancelByo:operationId completion:^{ resolve(nil); }];
|
|
48
|
+
}
|
|
28
49
|
- (void)cancel:(NSString *)operationId resolve:(RCTPromiseResolveBlock)resolve reject:(__unused RCTPromiseRejectBlock)reject {
|
|
29
50
|
[_backend cancel:operationId completion:^{ resolve(nil); }];
|
|
30
51
|
}
|
|
@@ -4,9 +4,28 @@ import Foundation
|
|
|
4
4
|
@objc(BotaUploadV2BackendBridge)
|
|
5
5
|
public final class BotaUploadV2BackendBridge: NSObject, @unchecked Sendable {
|
|
6
6
|
@MainActor private var coordinator: UploadCoordinator?
|
|
7
|
+
@MainActor private var byo: ByoBridgeDriver?
|
|
7
8
|
@MainActor private var destroyed = false
|
|
9
|
+
@MainActor private var statusJobs: [String: Task<Void, Never>] = [:]
|
|
8
10
|
@MainActor private var credentialsRequested: (@Sendable (String, String) -> Void)?
|
|
9
11
|
|
|
12
|
+
@objc(readProtectedStreamingStatus:inputJSON:completion:)
|
|
13
|
+
public func readProtectedStreamingStatus(_ id: String, inputJSON: String,
|
|
14
|
+
completion: @escaping @Sendable (String?, NSError?) -> Void) {
|
|
15
|
+
Task { @MainActor in
|
|
16
|
+
guard !destroyed, statusJobs[id] == nil else { completion(nil, UploadFailure.cancelled as NSError); return }
|
|
17
|
+
statusJobs[id] = Task { @MainActor in
|
|
18
|
+
defer { statusJobs[id] = nil }
|
|
19
|
+
do { let result = try await UploadStreamingStatus().read(inputJSON); completion(result, nil) }
|
|
20
|
+
catch { completion(nil, UploadFailure.safe(error) as NSError) }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
@objc(cancelProtectedStreamingStatus:completion:)
|
|
25
|
+
public func cancelProtectedStreamingStatus(_ id: String, completion: @escaping @Sendable () -> Void) {
|
|
26
|
+
Task { @MainActor in statusJobs[id]?.cancel(); completion() }
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
@objc(prepare:credentialsRequested:completion:)
|
|
11
30
|
public func prepare(_ json: String,
|
|
12
31
|
credentialsRequested: @escaping @Sendable (String, String) -> Void,
|
|
@@ -23,6 +42,26 @@ public final class BotaUploadV2BackendBridge: NSObject, @unchecked Sendable {
|
|
|
23
42
|
}
|
|
24
43
|
}
|
|
25
44
|
|
|
45
|
+
@objc(syncByo:credentialsRequested:completion:)
|
|
46
|
+
public func syncByo(_ json: String, credentialsRequested: @escaping @Sendable (String, String) -> Void,
|
|
47
|
+
completion: @escaping @Sendable (String?, NSError?) -> Void) {
|
|
48
|
+
Task { @MainActor in
|
|
49
|
+
do {
|
|
50
|
+
guard !destroyed else { throw UploadFailure.cancelled }
|
|
51
|
+
self.credentialsRequested = credentialsRequested
|
|
52
|
+
completion(try await byoService().sync(json), nil)
|
|
53
|
+
} catch { completion(nil, (error is ByoStorageError ? error : UploadFailure.safe(error)) as NSError) }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
@objc(cancelByo:completion:)
|
|
57
|
+
public func cancelByo(_ id: String, completion: @escaping @Sendable () -> Void) {
|
|
58
|
+
Task { @MainActor in await byoService().cancel(id); completion() }
|
|
59
|
+
}
|
|
60
|
+
@MainActor private func byoService() -> ByoBridgeDriver {
|
|
61
|
+
if let byo { return byo }
|
|
62
|
+
let value = ByoBridgeDriver(credentials: service().credentials); byo = value; return value
|
|
63
|
+
}
|
|
64
|
+
|
|
26
65
|
@objc(cancel:completion:)
|
|
27
66
|
public func cancel(_ id: String, completion: @escaping @Sendable () -> Void) {
|
|
28
67
|
Task { @MainActor in coordinator?.cancel(id); completion() }
|
|
@@ -45,7 +84,11 @@ public final class BotaUploadV2BackendBridge: NSObject, @unchecked Sendable {
|
|
|
45
84
|
Task { @MainActor in coordinator?.credentials.reject(requestId: id); completion() }
|
|
46
85
|
}
|
|
47
86
|
@objc public func invalidate() {
|
|
48
|
-
Task { @MainActor in
|
|
87
|
+
Task { @MainActor in
|
|
88
|
+
destroyed = true; coordinator?.cancelAll(); credentialsRequested = nil
|
|
89
|
+
statusJobs.values.forEach { $0.cancel() }; statusJobs.removeAll()
|
|
90
|
+
await byo?.stop()
|
|
91
|
+
}
|
|
49
92
|
}
|
|
50
93
|
|
|
51
94
|
@MainActor private func service() -> UploadCoordinator {
|
|
@@ -69,6 +112,12 @@ public final class BotaUploadV2BackendBridge: NSObject, @unchecked Sendable {
|
|
|
69
112
|
default: throw UploadFailure.invalidDocument
|
|
70
113
|
}
|
|
71
114
|
guard let sessionId = UUID(uuidString: prepared.session.session_id) else { throw UploadFailure.invalidDocument }
|
|
115
|
+
let submitMarkers: EncryptedUploadV2Material.MarkerSubmitter?
|
|
116
|
+
if operation.input.recording.markersRequired == true {
|
|
117
|
+
submitMarkers = { documents, evidence in
|
|
118
|
+
try await operation.submitMarkers(documents, evidence: Self.evidence(evidence))
|
|
119
|
+
}
|
|
120
|
+
} else { submitMarkers = nil }
|
|
72
121
|
let material = EncryptedUploadV2Material(
|
|
73
122
|
materialID: UUID().uuidString.lowercased(), recordingID: operation.input.recording.uuid,
|
|
74
123
|
uploadSessionID: sessionId, ownerRevision: prepared.session.owner_revision, policy: policy,
|
|
@@ -83,7 +132,8 @@ public final class BotaUploadV2BackendBridge: NSObject, @unchecked Sendable {
|
|
|
83
132
|
return EncryptedUploadV2ContextExchange(challenge: context.challenge, exchangeProof: context.exchangeProof)
|
|
84
133
|
},
|
|
85
134
|
shouldUploadCiphertext: { try await operation.shouldUpload(Self.evidence($0)) },
|
|
86
|
-
reconcileStaging: { try await operation.reconcileStaging($0, evidence: Self.evidence($1)) }
|
|
135
|
+
reconcileStaging: { try await operation.reconcileStaging($0, evidence: Self.evidence($1)) },
|
|
136
|
+
submitMarkers: submitMarkers
|
|
87
137
|
)
|
|
88
138
|
return BotaDeviceSDKEncryptedUploadV2Materials.register(material)
|
|
89
139
|
}, unregister: { id in _ = BotaDeviceSDKEncryptedUploadV2Materials.remove(id) })
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import BotaAppSDK
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Metadata-only RN boundary. SDK native code owns documents and recording bytes.
|
|
5
|
+
@MainActor final class ByoBridgeDriver {
|
|
6
|
+
private let credentials: UploadCredentials
|
|
7
|
+
private var tasks: [String: Task<String, Error>] = [:]
|
|
8
|
+
private var cancelled: Set<String> = []
|
|
9
|
+
private var stopped = false
|
|
10
|
+
init(credentials: UploadCredentials) { self.credentials = credentials }
|
|
11
|
+
private struct Input: Decodable {
|
|
12
|
+
let operationId: String
|
|
13
|
+
let device: Device
|
|
14
|
+
let recording: Recording?
|
|
15
|
+
let mode: String
|
|
16
|
+
let scope: Scope
|
|
17
|
+
struct Recording: Decodable { let uuid: String; let generation: UInt32 }
|
|
18
|
+
struct Scope: Decodable {
|
|
19
|
+
let baseUrl, scopeKey, organizationId, projectId, environment, deviceId: String
|
|
20
|
+
let bindingGeneration: UInt32
|
|
21
|
+
}
|
|
22
|
+
struct Device: Decodable {
|
|
23
|
+
let id, serialNumber, deviceType, firmwareVersion, connectionState: String
|
|
24
|
+
let hardwareRevision: String?
|
|
25
|
+
let isProvisioned: Bool
|
|
26
|
+
let mtu: Int
|
|
27
|
+
func native() throws -> ConnectedDevice {
|
|
28
|
+
let type: DeviceType
|
|
29
|
+
switch deviceType {
|
|
30
|
+
case "bota_pin": type = .botaPin
|
|
31
|
+
case "bota_pin_4g": type = .botaPin4G
|
|
32
|
+
case "bota_note": type = .botaNote
|
|
33
|
+
default: throw UploadFailure.invalidInput
|
|
34
|
+
}
|
|
35
|
+
guard connectionState == "connected", (23...517).contains(mtu),
|
|
36
|
+
!id.isEmpty, !serialNumber.isEmpty else { throw UploadFailure.invalidInput }
|
|
37
|
+
return ConnectedDevice(id: id, serialNumber: serialNumber, deviceType: type, firmwareVersion: firmwareVersion,
|
|
38
|
+
hardwareRevision: hardwareRevision, isProvisioned: isProvisioned, connectionState: .connected, mtu: mtu)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
private func check(_ id: String) throws {
|
|
43
|
+
try Task.checkCancellation()
|
|
44
|
+
guard !stopped, !cancelled.contains(id), tasks[id] != nil else { throw UploadFailure.cancelled }
|
|
45
|
+
}
|
|
46
|
+
func sync(_ json: String) async throws -> String {
|
|
47
|
+
guard json.utf8.count <= 8192 else { throw UploadFailure.invalidInput }
|
|
48
|
+
let input = try JSONDecoder().decode(Input.self, from: Data(json.utf8))
|
|
49
|
+
let id = input.operationId
|
|
50
|
+
guard id.hasPrefix("byo-"), id.utf8.count <= 64, !stopped, !cancelled.contains(id), tasks[id] == nil,
|
|
51
|
+
let url = URL(string: input.scope.baseUrl) else { throw UploadFailure.invalidInput }
|
|
52
|
+
let device = try input.device.native()
|
|
53
|
+
let task = Task {
|
|
54
|
+
try self.check(id)
|
|
55
|
+
let options = ByoStorageOptions(baseURL: url, scopeKey: input.scope.scopeKey,
|
|
56
|
+
organizationID: input.scope.organizationId, projectID: input.scope.projectId, deviceID: input.scope.deviceId,
|
|
57
|
+
environment: input.scope.environment, bindingGeneration: input.scope.bindingGeneration,
|
|
58
|
+
stateDirectory: FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
59
|
+
.appendingPathComponent("BotaSDKByo", isDirectory: true),
|
|
60
|
+
accessToken: { try await self.credentials.request(operationId: id) },
|
|
61
|
+
validateScope: { try await self.check(id) })
|
|
62
|
+
let bytes: Data
|
|
63
|
+
if input.mode == "list" {
|
|
64
|
+
let recordings = try await BotaDeviceClient.shared.recordings.listByoRecordings(device, options: options)
|
|
65
|
+
try self.check(id)
|
|
66
|
+
bytes = try JSONSerialization.data(withJSONObject: recordings.map { ["uuid": $0.uuid, "generation": $0.generation] as [String: Any] })
|
|
67
|
+
} else {
|
|
68
|
+
guard input.mode == "stream" || (input.mode == "sync" && input.recording != nil) else { throw UploadFailure.invalidInput }
|
|
69
|
+
let selected = input.recording.map { ByoRecording(uuid: $0.uuid, generation: $0.generation) }
|
|
70
|
+
let result: ByoUploadResult
|
|
71
|
+
if input.mode == "stream" { result = try await BotaDeviceClient.shared.recordings.streamByoRecording(device, recording: selected, options: options) }
|
|
72
|
+
else { result = try await BotaDeviceClient.shared.recordings.syncByoRecording(device, recording: selected!, options: options) }
|
|
73
|
+
try self.check(id)
|
|
74
|
+
bytes = try JSONSerialization.data(withJSONObject: ["recordingId": result.recordingID, "uploadSessionId": result.uploadSessionID])
|
|
75
|
+
}
|
|
76
|
+
return String(decoding: bytes, as: UTF8.self)
|
|
77
|
+
}
|
|
78
|
+
tasks[id] = task
|
|
79
|
+
defer { tasks[id] = nil; cancelled.remove(id); credentials.cancel(operationId: id) }
|
|
80
|
+
return try await task.value
|
|
81
|
+
}
|
|
82
|
+
func cancel(_ id: String) async {
|
|
83
|
+
guard id.hasPrefix("byo-"), id.utf8.count <= 64 else { return }
|
|
84
|
+
if cancelled.count >= 128 { stopped = true }
|
|
85
|
+
else { cancelled.insert(id) }
|
|
86
|
+
let task = tasks[id]; task?.cancel(); credentials.cancel(operationId: id); _ = await task?.result
|
|
87
|
+
}
|
|
88
|
+
func stop() async {
|
|
89
|
+
stopped = true
|
|
90
|
+
let current = tasks
|
|
91
|
+
for (id, task) in current { task.cancel(); credentials.cancel(operationId: id) }
|
|
92
|
+
for task in current.values { _ = await task.result }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -101,6 +101,7 @@ struct UploadInput: Codable, Sendable {
|
|
|
101
101
|
var durationMs: String
|
|
102
102
|
var plaintextLength: String
|
|
103
103
|
var storageFormat: UInt8
|
|
104
|
+
var markersRequired: Bool? = nil
|
|
104
105
|
}
|
|
105
106
|
struct Capability: Codable, Sendable { var rawValueHex: String; var flags: UInt32 }
|
|
106
107
|
struct Checkpoint: Codable, Sendable {
|
|
@@ -187,7 +188,9 @@ struct UploadInput: Codable, Sendable {
|
|
|
187
188
|
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
|
|
188
189
|
var identityScope = scope
|
|
189
190
|
if !includeTransport { identityScope.nativeDeviceId = "" }
|
|
190
|
-
|
|
191
|
+
var identityRecording = recording
|
|
192
|
+
if identityRecording.markersRequired == false { identityRecording.markersRequired = nil }
|
|
193
|
+
return UploadValidation.hash(try encoder.encode(Identity(scope: identityScope, recording: identityRecording)))
|
|
191
194
|
}
|
|
192
195
|
|
|
193
196
|
func validateCheckpoint(_ entry: UploadJournalEntry?) throws {
|
|
@@ -242,6 +245,8 @@ struct UploadSession: Decodable, Sendable {
|
|
|
242
245
|
let plaintext_length: UInt64?
|
|
243
246
|
let completion_receipt_base64: String?
|
|
244
247
|
let completion_receipt_sha256: String?
|
|
248
|
+
let marker_completion_receipt_base64: String?
|
|
249
|
+
let marker_completion_receipt_sha256: String?
|
|
245
250
|
|
|
246
251
|
func validatePointer() throws {
|
|
247
252
|
guard profile == "encrypted_upload_v2", UploadValidation.uuid(session_id), owner_revision > 0,
|
|
@@ -178,7 +178,7 @@ struct UploadContext: Sendable {
|
|
|
178
178
|
} else {
|
|
179
179
|
let nonce = try await nonce()
|
|
180
180
|
let body: [String: Any] = [
|
|
181
|
-
"device_id": input.scope.deviceId, "
|
|
181
|
+
"device_id": input.scope.deviceId, "binding_generation": input.scope.bindingGeneration,
|
|
182
182
|
"recording_uuid": input.recording.uuid, "recording_generation": input.recording.generation,
|
|
183
183
|
"storage_format": "bota_enc_v2", "channel": "ble",
|
|
184
184
|
"capabilities_base64": try UploadValidation.bytes(input.capability.rawValueHex).base64EncodedString(),
|
|
@@ -201,7 +201,28 @@ struct UploadContext: Sendable {
|
|
|
201
201
|
try selected.validatePointer()
|
|
202
202
|
_ = try UploadValidation.date(selected.expires_at)
|
|
203
203
|
guard UploadValidation.hex(selected.authorization_sha256, bytes: 32) else { throw UploadFailure.invalidDocument }
|
|
204
|
-
|
|
204
|
+
var authorization = try UploadValidation.document(selected.authorization_base64, hash: selected.authorization_sha256, size: 408)
|
|
205
|
+
if input.recording.markersRequired == true {
|
|
206
|
+
struct Admission: Decodable {
|
|
207
|
+
let profile: String
|
|
208
|
+
let context_base64: String
|
|
209
|
+
let authorization_base64: String
|
|
210
|
+
let authorization_sha256: String
|
|
211
|
+
}
|
|
212
|
+
let path = try sessionPath() + "/markers/authorization"
|
|
213
|
+
let admitted: Data
|
|
214
|
+
do {
|
|
215
|
+
admitted = try await retry { try await request(path + "?owner_revision=\(selected.owner_revision)") }
|
|
216
|
+
} catch UploadFailure.http(404) where ["", "created", "staging", "staged"].contains(selected.state ?? "") {
|
|
217
|
+
admitted = try await retry { try await request(path, method: "POST", body: [
|
|
218
|
+
"owner_revision": selected.owner_revision, "request_uuid": selected.session_id,
|
|
219
|
+
]) }
|
|
220
|
+
}
|
|
221
|
+
let admission: Admission = try decode(admitted)
|
|
222
|
+
guard admission.profile == "recording-markers/1" else { throw UploadFailure.invalidDocument }
|
|
223
|
+
authorization.append(try UploadValidation.document(admission.context_base64, size: 176))
|
|
224
|
+
authorization.append(try UploadValidation.document(admission.authorization_base64, hash: admission.authorization_sha256, size: 280))
|
|
225
|
+
}
|
|
205
226
|
session = selected
|
|
206
227
|
return UploadPrepared(session: selected, authorization: authorization, recordingId: initial.recordingId)
|
|
207
228
|
}
|
|
@@ -313,6 +334,18 @@ struct UploadContext: Sendable {
|
|
|
313
334
|
needsManifest = false
|
|
314
335
|
}
|
|
315
336
|
|
|
337
|
+
func submitMarkers(_ documents: [Data], evidence: UploadEvidence) async throws {
|
|
338
|
+
try verify(evidence)
|
|
339
|
+
guard input.recording.markersRequired == true, (2...4097).contains(documents.count),
|
|
340
|
+
documents.first?.count == 200, documents.dropFirst().allSatisfy({ (216...402).contains($0.count) }),
|
|
341
|
+
let revision = entry?.ownerRevision else { throw UploadFailure.invalidDocument }
|
|
342
|
+
let path = try sessionPath() + "/markers/batch"
|
|
343
|
+
_ = try await retry { try await request(path, method: "POST", body: [
|
|
344
|
+
"owner_revision": revision, "seal_base64": documents[0].base64EncodedString(),
|
|
345
|
+
"pages_base64": documents.dropFirst().map { $0.base64EncodedString() },
|
|
346
|
+
]) }
|
|
347
|
+
}
|
|
348
|
+
|
|
316
349
|
func finalize(_ evidence: UploadEvidence) async throws {
|
|
317
350
|
try verify(evidence)
|
|
318
351
|
guard let entry else { throw UploadFailure.state }
|
|
@@ -325,7 +358,12 @@ struct UploadContext: Sendable {
|
|
|
325
358
|
let hash = status.completion_receipt_sha256, UploadValidation.hex(hash, bytes: 32) else {
|
|
326
359
|
throw UploadFailure.invalidDocument
|
|
327
360
|
}
|
|
328
|
-
|
|
361
|
+
var completed = try UploadValidation.document(status.completion_receipt_base64, hash: hash, size: 336)
|
|
362
|
+
if input.recording.markersRequired == true {
|
|
363
|
+
guard let markerHash = status.marker_completion_receipt_sha256 else { throw UploadFailure.invalidDocument }
|
|
364
|
+
completed.append(try UploadValidation.document(status.marker_completion_receipt_base64, hash: markerHash, size: 296))
|
|
365
|
+
}
|
|
366
|
+
receipt = completed
|
|
329
367
|
return
|
|
330
368
|
}
|
|
331
369
|
try await wait(2000)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreFoundation
|
|
3
|
+
|
|
4
|
+
/** Scalar-only read boundary; all HTTP runs through the no-redirect native client. */
|
|
5
|
+
@MainActor final class UploadStreamingStatus {
|
|
6
|
+
private let http: any UploadHTTP
|
|
7
|
+
init(http: any UploadHTTP = UploadURLSessionHTTP()) { self.http = http }
|
|
8
|
+
func read(_ input: String) async throws -> String {
|
|
9
|
+
guard let object = try JSONSerialization.jsonObject(with: Data(input.utf8)) as? [String: Any],
|
|
10
|
+
let address = object["url"] as? String, let token = object["token"] as? String,
|
|
11
|
+
let org = object["organizationId"] as? String,
|
|
12
|
+
UploadValidation.matches(address, "https://[A-Za-z0-9.-]+(?::[0-9]+)?(?:/[A-Za-z0-9_-]+)+/recordings/rec_[A-Za-z0-9_-]+/streaming-status\\?session_id=[0-9a-f-]{36}"),
|
|
13
|
+
let url = URL(string: address), (1...16384).contains(token.utf8.count),
|
|
14
|
+
token.utf8.allSatisfy({ (33...126).contains($0) }),
|
|
15
|
+
org.isEmpty || UploadValidation.matches(org, "[A-Za-z0-9_-]{1,128}") else { throw UploadFailure.invalidInput }
|
|
16
|
+
try Task.checkCancellation()
|
|
17
|
+
var request = URLRequest(url: url); request.httpMethod = "GET"
|
|
18
|
+
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
19
|
+
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
20
|
+
if !org.isEmpty { request.setValue(org, forHTTPHeaderField: "X-Organization-Id") }
|
|
21
|
+
let response = try await http.send(request)
|
|
22
|
+
try Task.checkCancellation()
|
|
23
|
+
guard response.status == 200 else { throw UploadFailure.http(response.status) }
|
|
24
|
+
guard response.body.count <= 16384 else { throw UploadFailure.invalidDocument }
|
|
25
|
+
return try Self.scalarStatus(response.body)
|
|
26
|
+
}
|
|
27
|
+
static func scalarStatus(_ body: Data) throws -> String {
|
|
28
|
+
let strings: Set<String> = ["profile", "recording_id", "session_id", "writer_epoch", "revision", "state"]
|
|
29
|
+
let numbers: Set<String> = ["recording_generation", "received_count", "contiguous_sequence"]
|
|
30
|
+
guard let object = try JSONSerialization.jsonObject(with: body) as? [String: Any],
|
|
31
|
+
Set(object.keys) == strings.union(numbers).union(["expected_count", "authorization_expired"]),
|
|
32
|
+
strings.allSatisfy({ (object[$0] as? String).map { $0.utf8.count <= 160 } == true }),
|
|
33
|
+
numbers.allSatisfy({ isNumber(object[$0]) }),
|
|
34
|
+
object["expected_count"] is NSNull || isNumber(object["expected_count"]),
|
|
35
|
+
let expired = object["authorization_expired"] as? NSNumber,
|
|
36
|
+
CFGetTypeID(expired) == CFBooleanGetTypeID() else { throw UploadFailure.invalidDocument }
|
|
37
|
+
return String(decoding: try JSONSerialization.data(withJSONObject: object), as: UTF8.self)
|
|
38
|
+
}
|
|
39
|
+
private static func isNumber(_ value: Any?) -> Bool {
|
|
40
|
+
guard let number = value as? NSNumber else { return false }
|
|
41
|
+
return CFGetTypeID(number) != CFBooleanGetTypeID()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createManagedByoStorageBackend = createManagedByoStorageBackend;
|
|
7
|
+
/** @internal Shared implementation for the production factory and host tests. */
|
|
8
|
+
function createManagedByoStorageBackend(native, options) {
|
|
9
|
+
const config = {
|
|
10
|
+
...options
|
|
11
|
+
};
|
|
12
|
+
const url = new URL(config.baseUrl);
|
|
13
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || !config.scopeKey || !config.organizationId || !config.projectId || !config.signal || !['development', 'gamma', 'production'].includes(config.environment)) throw new Error('Invalid BYO configuration');
|
|
14
|
+
let disposed = false;
|
|
15
|
+
const attempts = new Set();
|
|
16
|
+
const perform = async (device, recording, target, live = false) => {
|
|
17
|
+
if (!native?.syncByo || !native.cancelByo) throw new Error('BYO requires the matching native SDK binary');
|
|
18
|
+
device = {
|
|
19
|
+
...device
|
|
20
|
+
};
|
|
21
|
+
recording = recording && {
|
|
22
|
+
...recording
|
|
23
|
+
};
|
|
24
|
+
target = {
|
|
25
|
+
...target
|
|
26
|
+
};
|
|
27
|
+
if (recording !== null && (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(recording.uuid) || !Number.isSafeInteger(recording.generation) || recording.generation < 0 || recording.generation > 0xffffffff) || !Number.isSafeInteger(target.bindingGeneration) || target.bindingGeneration < 1 || target.bindingGeneration > 0xffffffff) {
|
|
28
|
+
throw new Error('Invalid BYO recording identity');
|
|
29
|
+
}
|
|
30
|
+
const operationId = 'byo-' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, key => {
|
|
31
|
+
const value = Math.floor(Math.random() * 16);
|
|
32
|
+
return (key === 'x' ? value : value & 3 | 8).toString(16);
|
|
33
|
+
});
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
let cancelTask;
|
|
36
|
+
let settle;
|
|
37
|
+
const settled = new Promise(resolve => {
|
|
38
|
+
settle = resolve;
|
|
39
|
+
});
|
|
40
|
+
const check = () => {
|
|
41
|
+
if (disposed || controller.signal.aborted || config.signal.aborted || target.signal?.aborted) throw new Error('BYO upload cancelled');
|
|
42
|
+
};
|
|
43
|
+
const cancel = () => {
|
|
44
|
+
controller.abort();
|
|
45
|
+
return cancelTask ??= Promise.resolve().then(() => native.cancelByo(operationId));
|
|
46
|
+
};
|
|
47
|
+
const abort = () => {
|
|
48
|
+
void cancel().catch(() => {});
|
|
49
|
+
};
|
|
50
|
+
const attempt = {
|
|
51
|
+
cancel,
|
|
52
|
+
settled
|
|
53
|
+
};
|
|
54
|
+
attempts.add(attempt);
|
|
55
|
+
let subscription;
|
|
56
|
+
let completed = false;
|
|
57
|
+
try {
|
|
58
|
+
config.signal.addEventListener('abort', abort, {
|
|
59
|
+
once: true
|
|
60
|
+
});
|
|
61
|
+
target.signal?.addEventListener('abort', abort, {
|
|
62
|
+
once: true
|
|
63
|
+
});
|
|
64
|
+
check();
|
|
65
|
+
subscription = native.onCredentialsRequested(event => {
|
|
66
|
+
if (event.operationId !== operationId) return;
|
|
67
|
+
void (async () => {
|
|
68
|
+
try {
|
|
69
|
+
check();
|
|
70
|
+
const token = await config.getAccessToken(controller.signal);
|
|
71
|
+
check();
|
|
72
|
+
await native.resolveCredentials(event.requestId, token);
|
|
73
|
+
} catch {
|
|
74
|
+
await native.rejectCredentials(event.requestId).catch(() => {});
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
});
|
|
78
|
+
const value = JSON.parse(await native.syncByo(JSON.stringify({
|
|
79
|
+
operationId,
|
|
80
|
+
mode: live ? 'stream' : recording === null ? 'list' : 'sync',
|
|
81
|
+
device,
|
|
82
|
+
recording,
|
|
83
|
+
scope: {
|
|
84
|
+
baseUrl: url.href,
|
|
85
|
+
scopeKey: config.scopeKey,
|
|
86
|
+
organizationId: config.organizationId,
|
|
87
|
+
projectId: config.projectId,
|
|
88
|
+
environment: config.environment,
|
|
89
|
+
deviceId: target.deviceId,
|
|
90
|
+
bindingGeneration: target.bindingGeneration
|
|
91
|
+
}
|
|
92
|
+
})));
|
|
93
|
+
check();
|
|
94
|
+
if (recording === null && !live) {
|
|
95
|
+
if (!Array.isArray(value) || value.length > 4096 || value.some(item => !item || !/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(item.uuid) || !Number.isSafeInteger(item.generation) || item.generation < 0 || item.generation > 0xffffffff)) throw new Error('Invalid BYO native catalog');
|
|
96
|
+
completed = true;
|
|
97
|
+
return value.map(item => ({
|
|
98
|
+
uuid: item.uuid,
|
|
99
|
+
generation: item.generation
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
const result = value;
|
|
103
|
+
if (!result || !/^rec_[A-Za-z0-9_-]+$/.test(result.recordingId) || !/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(result.uploadSessionId)) throw new Error('Invalid BYO native result');
|
|
104
|
+
completed = true;
|
|
105
|
+
return {
|
|
106
|
+
recordingId: result.recordingId,
|
|
107
|
+
uploadSessionId: result.uploadSessionId
|
|
108
|
+
};
|
|
109
|
+
} finally {
|
|
110
|
+
try {
|
|
111
|
+
if (!completed) await cancel();
|
|
112
|
+
} finally {
|
|
113
|
+
attempts.delete(attempt);
|
|
114
|
+
config.signal.removeEventListener('abort', abort);
|
|
115
|
+
target.signal?.removeEventListener('abort', abort);
|
|
116
|
+
subscription?.remove();
|
|
117
|
+
settle();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
async sync(device, recording, target) {
|
|
123
|
+
return await perform(device, recording, target);
|
|
124
|
+
},
|
|
125
|
+
async stream(device, recording, target) {
|
|
126
|
+
return await perform(device, recording, target, true);
|
|
127
|
+
},
|
|
128
|
+
async list(device, target) {
|
|
129
|
+
return await perform(device, null, target);
|
|
130
|
+
},
|
|
131
|
+
async dispose() {
|
|
132
|
+
disposed = true;
|
|
133
|
+
const results = await Promise.allSettled([...attempts].map(async attempt => {
|
|
134
|
+
try {
|
|
135
|
+
await attempt.cancel();
|
|
136
|
+
} finally {
|
|
137
|
+
await attempt.settled;
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
if (results.some(result => result.status === 'rejected')) throw new Error('BYO cancellation failed');
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=byoStorageBackend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["createManagedByoStorageBackend","native","options","config","url","URL","baseUrl","protocol","username","password","search","hash","scopeKey","organizationId","projectId","signal","includes","environment","Error","disposed","attempts","Set","perform","device","recording","target","live","syncByo","cancelByo","test","uuid","Number","isSafeInteger","generation","bindingGeneration","operationId","replace","key","value","Math","floor","random","toString","controller","AbortController","cancelTask","settle","settled","Promise","resolve","check","aborted","cancel","abort","then","catch","attempt","add","subscription","completed","addEventListener","once","onCredentialsRequested","event","token","getAccessToken","resolveCredentials","requestId","rejectCredentials","JSON","parse","stringify","mode","scope","href","deviceId","Array","isArray","length","some","item","map","result","recordingId","uploadSessionId","delete","removeEventListener","remove","sync","stream","list","dispose","results","allSettled","status"],"sourceRoot":"../../src","sources":["byoStorageBackend.ts"],"mappings":"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","ignoreList":[]}
|
package/lib/commonjs/client.js
CHANGED
|
@@ -86,7 +86,7 @@ const mapPendingRecording = value => {
|
|
|
86
86
|
}
|
|
87
87
|
const recording = value.encrypted;
|
|
88
88
|
const plaintextLength = recording.plaintextLength;
|
|
89
|
-
if (typeof recording.uuid !== 'string' || !uuidPattern.test(recording.uuid) || recording.storageFormat !== 3 || typeof plaintextLength !== 'string' || !/^(0|[1-9][0-9]{0,19})$/.test(plaintextLength) || BigInt(plaintextLength) > 0xffffffffffffffffn || typeof recording.ciphertextLength !== 'string' || !/^[1-9][0-9]{0,19}$/.test(recording.ciphertextLength) || BigInt(recording.ciphertextLength) > 0xffffffffffffffffn || typeof recording.ciphertextSha256 !== 'string' || !/^[0-9a-f]{64}$/.test(recording.ciphertextSha256) || !Number.isInteger(recording.generation) || recording.generation < 0 || recording.generation > 0xffffffff) {
|
|
89
|
+
if (typeof recording.uuid !== 'string' || !uuidPattern.test(recording.uuid) || recording.storageFormat !== 3 || typeof plaintextLength !== 'string' || recording.markersRequired !== undefined && typeof recording.markersRequired !== 'boolean' || !/^(0|[1-9][0-9]{0,19})$/.test(plaintextLength) || BigInt(plaintextLength) > 0xffffffffffffffffn || typeof recording.ciphertextLength !== 'string' || !/^[1-9][0-9]{0,19}$/.test(recording.ciphertextLength) || BigInt(recording.ciphertextLength) > 0xffffffffffffffffn || typeof recording.ciphertextSha256 !== 'string' || !/^[0-9a-f]{64}$/.test(recording.ciphertextSha256) || !Number.isInteger(recording.generation) || recording.generation < 0 || recording.generation > 0xffffffff) {
|
|
90
90
|
throw new Error('invalid native recording catalog identity');
|
|
91
91
|
}
|
|
92
92
|
return {
|
|
@@ -97,6 +97,7 @@ const mapPendingRecording = value => {
|
|
|
97
97
|
storageFormat: 3,
|
|
98
98
|
plaintextLength,
|
|
99
99
|
startedAtMs: recording.startedAtMs,
|
|
100
|
+
markersRequired: recording.markersRequired ?? false,
|
|
100
101
|
startedAt: new Date(catalogInteger(recording.startedAtMs, 8640000000000000n)),
|
|
101
102
|
durationMs: catalogInteger(recording.durationMs)
|
|
102
103
|
};
|